summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apct-tests/perftests/autofill/Android.bp1
-rw-r--r--apct-tests/perftests/autofill/src/android/view/autofill/AbstractAutofillPerfTestCase.java28
-rw-r--r--apct-tests/perftests/autofill/src/android/view/autofill/AutofillTestWatcher.java133
-rw-r--r--apct-tests/perftests/autofill/src/android/view/autofill/LoginTest.java34
-rw-r--r--apct-tests/perftests/autofill/src/android/view/autofill/MyAutofillService.java20
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobScheduler.java15
-rw-r--r--apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java215
-rw-r--r--apex/jobscheduler/service/java/com/android/server/alarm/BatchingAlarmStore.java2
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java18
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java199
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java9
-rw-r--r--apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java16
-rw-r--r--api/current.txt70
-rw-r--r--api/module-lib-current.txt6
-rw-r--r--api/system-current.txt92
-rw-r--r--api/system-lint-baseline.txt88
-rw-r--r--api/test-current.txt10
-rw-r--r--api/test-lint-baseline.txt230
-rw-r--r--cmds/statsd/src/atoms.proto66
-rw-r--r--cmds/statsd/src/condition/ConditionTracker.h6
-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/MetricProducer.cpp32
-rw-r--r--cmds/statsd/src/metrics/MetricProducer.h50
-rw-r--r--cmds/statsd/src/metrics/MetricsManager.cpp21
-rw-r--r--cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp247
-rw-r--r--cmds/statsd/src/metrics/parsing_utils/config_update_utils.h79
-rw-r--r--cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp301
-rw-r--r--cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h63
-rw-r--r--cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp599
-rw-r--r--core/java/android/app/ActivityManagerInternal.java27
-rw-r--r--core/java/android/app/ActivityThread.java450
-rw-r--r--core/java/android/app/AppOpsManager.java84
-rw-r--r--core/java/android/app/ApplicationPackageManager.java13
-rw-r--r--core/java/android/app/ClientTransactionHandler.java54
-rw-r--r--core/java/android/app/ContextImpl.java7
-rw-r--r--core/java/android/app/IActivityManager.aidl7
-rw-r--r--core/java/android/app/IActivityTaskManager.aidl6
-rw-r--r--core/java/android/app/LocalActivityManager.java43
-rw-r--r--core/java/android/app/StatusBarManager.java1
-rw-r--r--core/java/android/app/TEST_MAPPING4
-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/bluetooth/BluetoothPan.java35
-rw-r--r--core/java/android/content/Context.java9
-rw-r--r--core/java/android/content/om/TEST_MAPPING14
-rw-r--r--core/java/android/content/pm/ActivityInfo.java10
-rw-r--r--core/java/android/content/pm/ApkChecksum.java40
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java11
-rw-r--r--core/java/android/content/pm/Checksum.java27
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl5
-rw-r--r--core/java/android/content/pm/PackageInstaller.java3
-rw-r--r--core/java/android/content/pm/PackageManager.java12
-rw-r--r--core/java/android/content/pm/TEST_MAPPING14
-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/display/DisplayManager.java24
-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/soundtrigger/SoundTrigger.java184
-rw-r--r--core/java/android/hardware/soundtrigger/SoundTriggerModule.java34
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java23
-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/permission/PermissionManager.java15
-rw-r--r--core/java/android/preference/SeekBarVolumizer.java51
-rw-r--r--core/java/android/preference/VolumePreference.java5
-rw-r--r--core/java/android/provider/DeviceConfig.java9
-rw-r--r--core/java/android/provider/Settings.java23
-rw-r--r--core/java/android/provider/Telephony.java14
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java3
-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/telephony/PhoneStateListener.java32
-rw-r--r--core/java/android/telephony/TelephonyRegistryManager.java11
-rw-r--r--core/java/android/util/FeatureFlagUtils.java3
-rw-r--r--core/java/android/view/Choreographer.java14
-rw-r--r--core/java/android/view/DisplayEventReceiver.java12
-rw-r--r--core/java/android/view/FrameMetrics.java30
-rw-r--r--core/java/android/view/IWindowSession.aidl42
-rw-r--r--core/java/android/view/InsetsState.java22
-rw-r--r--core/java/android/view/View.java12
-rw-r--r--core/java/android/view/ViewRootImpl.java85
-rw-r--r--core/java/android/view/WindowManagerImpl.java19
-rw-r--r--core/java/android/view/inputmethod/EditorInfo.java47
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java4
-rw-r--r--core/java/android/window/WindowMetricsHelper.java17
-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/config/sysui/SystemUiDeviceConfigFlags.java11
-rw-r--r--core/java/com/android/internal/infra/AbstractRemoteService.java1
-rw-r--r--core/java/com/android/internal/listeners/ListenerExecutor.java86
-rw-r--r--core/java/com/android/internal/policy/DecorView.java92
-rw-r--r--core/java/com/android/internal/telephony/IPhoneStateListener.aidl2
-rw-r--r--core/jni/android_view_DisplayEventReceiver.cpp11
-rw-r--r--core/proto/android/content/configuration.proto1
-rw-r--r--core/proto/android/providers/settings/config.proto5
-rw-r--r--core/proto/android/providers/settings/global.proto2
-rw-r--r--core/proto/android/providers/settings/secure.proto2
-rw-r--r--core/proto/android/stats/hdmi/enums.proto122
-rw-r--r--core/res/AndroidManifest.xml24
-rw-r--r--core/res/res/values-am/strings.xml4
-rw-r--r--core/res/res/values-ar/strings.xml2
-rw-r--r--core/res/res/values-be/strings.xml26
-rw-r--r--core/res/res/values-gu/strings.xml6
-rw-r--r--core/res/res/values-in/strings.xml2
-rw-r--r--core/res/res/values-kk/strings.xml6
-rw-r--r--core/res/res/values-km/strings.xml2
-rw-r--r--core/res/res/values-nl/strings.xml18
-rw-r--r--core/res/res/values-pa/strings.xml2
-rw-r--r--core/res/res/values-uz/strings.xml2
-rw-r--r--core/res/res/values/attrs_manifest.xml10
-rw-r--r--core/res/res/values/config.xml5
-rw-r--r--core/res/res/values/strings.xml3
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--core/tests/PackageInstallerSessions/Android.bp5
-rw-r--r--core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt78
-rw-r--r--core/tests/coretests/src/android/app/activity/ActivityThreadTest.java62
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java64
-rw-r--r--core/tests/coretests/src/android/content/ContextTest.java38
-rw-r--r--core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java15
-rw-r--r--core/tests/coretests/src/android/view/InsetsControllerTest.java15
-rw-r--r--core/tests/coretests/src/android/view/InsetsStateTest.java338
-rw-r--r--core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java9
-rw-r--r--core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java12
-rw-r--r--core/tests/powertests/PowerStatsLoadTests/AndroidManifest.xml1
-rw-r--r--core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/PowerMetricsCollector.java47
-rw-r--r--data/etc/platform.xml7
-rw-r--r--data/etc/privapp-permissions-platform.xml1
-rw-r--r--data/etc/services.core.protolog.json24
-rw-r--r--data/fonts/fonts.xml29
-rw-r--r--data/keyboards/Vendor_27f8_Product_0bbe.kl54
-rw-r--r--graphics/java/android/graphics/FrameInfo.java26
-rw-r--r--graphics/java/android/graphics/HardwareRenderer.java3
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/sidecar/SettingsSidecarImpl.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java57
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java7
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java8
-rw-r--r--libs/hwui/FrameInfo.cpp3
-rw-r--r--libs/hwui/FrameInfo.h9
-rw-r--r--libs/hwui/jni/android_graphics_HardwareRenderer.cpp2
-rw-r--r--libs/hwui/pipeline/skia/GLFunctorDrawable.cpp9
-rw-r--r--libs/hwui/pipeline/skia/SkiaPipeline.cpp7
-rw-r--r--libs/hwui/pipeline/skia/VkFunctorDrawable.cpp2
-rw-r--r--libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp2
-rw-r--r--libs/hwui/renderthread/CanvasContext.cpp22
-rw-r--r--libs/hwui/renderthread/CanvasContext.h2
-rw-r--r--libs/hwui/renderthread/DrawFrameTask.cpp4
-rw-r--r--libs/hwui/renderthread/RenderThread.cpp4
-rw-r--r--libs/hwui/renderthread/RenderThread.h2
-rw-r--r--libs/hwui/renderthread/TimeLord.cpp14
-rw-r--r--libs/hwui/renderthread/TimeLord.h5
-rw-r--r--libs/hwui/tests/macrobench/TestSceneRunner.cpp6
-rw-r--r--location/java/android/location/ILocationManager.aidl2
-rw-r--r--location/java/android/location/LocationManager.java67
-rw-r--r--location/java/android/location/LocationRequest.java14
-rw-r--r--media/Android.bp24
-rw-r--r--media/TEST_MAPPING12
-rw-r--r--media/java/android/media/CamcorderProfile.java14
-rw-r--r--media/java/android/media/DrmInitData.java6
-rw-r--r--media/java/android/media/ExifInterface.java23
-rw-r--r--media/java/android/media/ExifInterfaceUtils.java18
-rw-r--r--media/java/android/media/MediaExtractor.java4
-rw-r--r--media/java/android/media/MediaTranscodeManager.java117
-rw-r--r--media/java/android/media/MediaTranscodingException.java38
-rw-r--r--media/java/android/media/RemoteController.java2
-rw-r--r--media/java/android/media/permission/ClearCallingIdentityContext.java58
-rw-r--r--media/java/android/media/permission/CompositeSafeCloseable.java40
-rw-r--r--media/java/android/media/permission/Identity.aidl32
-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.java (renamed from tests/utils/testutils/java/android/view/test/InsetsModeSession.java)24
-rw-r--r--media/java/android/media/session/MediaSessionManager.java55
-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.aidl53
-rw-r--r--media/jni/android_media_tv_Tuner.cpp15
-rw-r--r--media/jni/android_media_tv_Tuner.h1
-rw-r--r--media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerDiedTest.java22
-rw-r--r--media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodingBenchmark.java4
-rw-r--r--non-updatable-api/current.txt70
-rw-r--r--non-updatable-api/module-lib-current.txt6
-rw-r--r--non-updatable-api/system-current.txt90
-rw-r--r--packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml2
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java5
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java4
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java5
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java2
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java19
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java29
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowController.java3
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java27
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayPanelViewControllerTest.java29
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java156
-rw-r--r--packages/InputDevices/res/values-af/strings.xml2
-rw-r--r--packages/InputDevices/res/values-am/strings.xml2
-rw-r--r--packages/InputDevices/res/values-ar/strings.xml5
-rw-r--r--packages/InputDevices/res/values-as/strings.xml5
-rw-r--r--packages/InputDevices/res/values-az/strings.xml5
-rw-r--r--packages/InputDevices/res/values-b+sr+Latn/strings.xml5
-rw-r--r--packages/InputDevices/res/values-be/strings.xml5
-rw-r--r--packages/InputDevices/res/values-bg/strings.xml2
-rw-r--r--packages/InputDevices/res/values-bn/strings.xml5
-rw-r--r--packages/InputDevices/res/values-bs/strings.xml2
-rw-r--r--packages/InputDevices/res/values-ca/strings.xml2
-rw-r--r--packages/InputDevices/res/values-cs/strings.xml2
-rw-r--r--packages/InputDevices/res/values-da/strings.xml2
-rw-r--r--packages/InputDevices/res/values-de/strings.xml2
-rw-r--r--packages/InputDevices/res/values-el/strings.xml2
-rw-r--r--packages/InputDevices/res/values-en-rAU/strings.xml1
-rw-r--r--packages/InputDevices/res/values-en-rCA/strings.xml1
-rw-r--r--packages/InputDevices/res/values-en-rGB/strings.xml1
-rw-r--r--packages/InputDevices/res/values-en-rIN/strings.xml1
-rw-r--r--packages/InputDevices/res/values-en-rXC/strings.xml1
-rw-r--r--packages/InputDevices/res/values-es-rUS/strings.xml2
-rw-r--r--packages/InputDevices/res/values-es/strings.xml2
-rw-r--r--packages/InputDevices/res/values-et/strings.xml2
-rw-r--r--packages/InputDevices/res/values-eu/strings.xml2
-rw-r--r--packages/InputDevices/res/values-fa/strings.xml2
-rw-r--r--packages/InputDevices/res/values-fi/strings.xml5
-rw-r--r--packages/InputDevices/res/values-fr-rCA/strings.xml2
-rw-r--r--packages/InputDevices/res/values-fr/strings.xml2
-rw-r--r--packages/InputDevices/res/values-gl/strings.xml2
-rw-r--r--packages/InputDevices/res/values-gu/strings.xml2
-rw-r--r--packages/InputDevices/res/values-hi/strings.xml2
-rw-r--r--packages/InputDevices/res/values-hr/strings.xml2
-rw-r--r--packages/InputDevices/res/values-hu/strings.xml2
-rw-r--r--packages/InputDevices/res/values-hy/strings.xml2
-rw-r--r--packages/InputDevices/res/values-in/strings.xml2
-rw-r--r--packages/InputDevices/res/values-is/strings.xml2
-rw-r--r--packages/InputDevices/res/values-it/strings.xml2
-rw-r--r--packages/InputDevices/res/values-iw/strings.xml2
-rw-r--r--packages/InputDevices/res/values-ja/strings.xml2
-rw-r--r--packages/InputDevices/res/values-ka/strings.xml2
-rw-r--r--packages/InputDevices/res/values-kk/strings.xml5
-rw-r--r--packages/InputDevices/res/values-km/strings.xml2
-rw-r--r--packages/InputDevices/res/values-kn/strings.xml5
-rw-r--r--packages/InputDevices/res/values-ko/strings.xml5
-rw-r--r--packages/InputDevices/res/values-ky/strings.xml5
-rw-r--r--packages/InputDevices/res/values-lo/strings.xml2
-rw-r--r--packages/InputDevices/res/values-lt/strings.xml2
-rw-r--r--packages/InputDevices/res/values-lv/strings.xml5
-rw-r--r--packages/InputDevices/res/values-mk/strings.xml2
-rw-r--r--packages/InputDevices/res/values-ml/strings.xml5
-rw-r--r--packages/InputDevices/res/values-mn/strings.xml5
-rw-r--r--packages/InputDevices/res/values-mr/strings.xml5
-rw-r--r--packages/InputDevices/res/values-ms/strings.xml5
-rw-r--r--packages/InputDevices/res/values-my/strings.xml2
-rw-r--r--packages/InputDevices/res/values-nb/strings.xml2
-rw-r--r--packages/InputDevices/res/values-ne/strings.xml5
-rw-r--r--packages/InputDevices/res/values-nl/strings.xml4
-rw-r--r--packages/InputDevices/res/values-or/strings.xml5
-rw-r--r--packages/InputDevices/res/values-pa/strings.xml5
-rw-r--r--packages/InputDevices/res/values-pl/strings.xml2
-rw-r--r--packages/InputDevices/res/values-pt-rBR/strings.xml2
-rw-r--r--packages/InputDevices/res/values-pt-rPT/strings.xml2
-rw-r--r--packages/InputDevices/res/values-pt/strings.xml2
-rw-r--r--packages/InputDevices/res/values-ro/strings.xml2
-rw-r--r--packages/InputDevices/res/values-ru/strings.xml2
-rw-r--r--packages/InputDevices/res/values-si/strings.xml2
-rw-r--r--packages/InputDevices/res/values-sk/strings.xml5
-rw-r--r--packages/InputDevices/res/values-sl/strings.xml2
-rw-r--r--packages/InputDevices/res/values-sq/strings.xml2
-rw-r--r--packages/InputDevices/res/values-sr/strings.xml5
-rw-r--r--packages/InputDevices/res/values-sv/strings.xml2
-rw-r--r--packages/InputDevices/res/values-sw/strings.xml2
-rw-r--r--packages/InputDevices/res/values-ta/strings.xml5
-rw-r--r--packages/InputDevices/res/values-te/strings.xml2
-rw-r--r--packages/InputDevices/res/values-th/strings.xml2
-rw-r--r--packages/InputDevices/res/values-tl/strings.xml2
-rw-r--r--packages/InputDevices/res/values-tr/strings.xml2
-rw-r--r--packages/InputDevices/res/values-uk/strings.xml2
-rw-r--r--packages/InputDevices/res/values-ur/strings.xml2
-rw-r--r--packages/InputDevices/res/values-uz/strings.xml5
-rw-r--r--packages/InputDevices/res/values-vi/strings.xml2
-rw-r--r--packages/InputDevices/res/values-zh-rCN/strings.xml2
-rw-r--r--packages/InputDevices/res/values-zh-rHK/strings.xml2
-rw-r--r--packages/InputDevices/res/values-zh-rTW/strings.xml2
-rw-r--r--packages/InputDevices/res/values-zu/strings.xml2
-rw-r--r--packages/SettingsLib/HelpUtils/res/values-te/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-ar/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-bn/strings.xml4
-rw-r--r--packages/SettingsLib/res/values-fa/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-gu/strings.xml4
-rw-r--r--packages/SettingsLib/res/values-hi/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-hu/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-hy/strings.xml4
-rw-r--r--packages/SettingsLib/res/values-ne/strings.xml4
-rw-r--r--packages/SettingsLib/res/values-sw/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-te/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-th/strings.xml2
-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/MediaOutputSliceConstants.java6
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java36
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java1
-rw-r--r--packages/Shell/AndroidManifest.xml3
-rw-r--r--packages/SystemUI/AndroidManifest.xml2
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java4
-rw-r--r--packages/SystemUI/res-keyguard/values-te/strings.xml2
-rw-r--r--packages/SystemUI/res/drawable/ic_move_magnification.xml (renamed from packages/SystemUI/res/drawable/ic_move.xml)2
-rw-r--r--packages/SystemUI/res/layout/window_magnifier_view.xml17
-rw-r--r--packages/SystemUI/res/values-af/strings.xml27
-rw-r--r--packages/SystemUI/res/values-am/strings.xml27
-rw-r--r--packages/SystemUI/res/values-ar/strings.xml31
-rw-r--r--packages/SystemUI/res/values-as/strings.xml27
-rw-r--r--packages/SystemUI/res/values-az/strings.xml27
-rw-r--r--packages/SystemUI/res/values-b+sr+Latn/strings.xml27
-rw-r--r--packages/SystemUI/res/values-be/strings.xml29
-rw-r--r--packages/SystemUI/res/values-bg/strings.xml29
-rw-r--r--packages/SystemUI/res/values-bn/strings.xml27
-rw-r--r--packages/SystemUI/res/values-bs/strings.xml27
-rw-r--r--packages/SystemUI/res/values-ca/strings.xml27
-rw-r--r--packages/SystemUI/res/values-cs/strings.xml31
-rw-r--r--packages/SystemUI/res/values-da/strings.xml29
-rw-r--r--packages/SystemUI/res/values-de/strings.xml27
-rw-r--r--packages/SystemUI/res/values-el/strings.xml29
-rw-r--r--packages/SystemUI/res/values-en-rAU/strings.xml20
-rw-r--r--packages/SystemUI/res/values-en-rCA/strings.xml20
-rw-r--r--packages/SystemUI/res/values-en-rGB/strings.xml20
-rw-r--r--packages/SystemUI/res/values-en-rIN/strings.xml20
-rw-r--r--packages/SystemUI/res/values-en-rXC/strings.xml20
-rw-r--r--packages/SystemUI/res/values-es-rUS/strings.xml37
-rw-r--r--packages/SystemUI/res/values-es/strings.xml31
-rw-r--r--packages/SystemUI/res/values-et/strings.xml27
-rw-r--r--packages/SystemUI/res/values-eu/strings.xml27
-rw-r--r--packages/SystemUI/res/values-fa/strings.xml27
-rw-r--r--packages/SystemUI/res/values-fi/strings.xml27
-rw-r--r--packages/SystemUI/res/values-fr-rCA/strings.xml27
-rw-r--r--packages/SystemUI/res/values-fr/strings.xml27
-rw-r--r--packages/SystemUI/res/values-gl/strings.xml33
-rw-r--r--packages/SystemUI/res/values-gu/strings.xml29
-rw-r--r--packages/SystemUI/res/values-hi/strings.xml33
-rw-r--r--packages/SystemUI/res/values-hr/strings.xml27
-rw-r--r--packages/SystemUI/res/values-hu/strings.xml27
-rw-r--r--packages/SystemUI/res/values-hy/strings.xml29
-rw-r--r--packages/SystemUI/res/values-in/strings.xml27
-rw-r--r--packages/SystemUI/res/values-is/strings.xml27
-rw-r--r--packages/SystemUI/res/values-it/strings.xml29
-rw-r--r--packages/SystemUI/res/values-iw/strings.xml27
-rw-r--r--packages/SystemUI/res/values-ja/strings.xml27
-rw-r--r--packages/SystemUI/res/values-ka/strings.xml29
-rw-r--r--packages/SystemUI/res/values-kk/strings.xml37
-rw-r--r--packages/SystemUI/res/values-km/strings.xml31
-rw-r--r--packages/SystemUI/res/values-kn/strings.xml27
-rw-r--r--packages/SystemUI/res/values-ko/strings.xml29
-rw-r--r--packages/SystemUI/res/values-ky/strings.xml29
-rw-r--r--packages/SystemUI/res/values-lo/strings.xml27
-rw-r--r--packages/SystemUI/res/values-lt/strings.xml29
-rw-r--r--packages/SystemUI/res/values-lv/strings.xml29
-rw-r--r--packages/SystemUI/res/values-mk/strings.xml31
-rw-r--r--packages/SystemUI/res/values-ml/strings.xml27
-rw-r--r--packages/SystemUI/res/values-mn/strings.xml29
-rw-r--r--packages/SystemUI/res/values-mr/strings.xml27
-rw-r--r--packages/SystemUI/res/values-ms/strings.xml27
-rw-r--r--packages/SystemUI/res/values-my/strings.xml27
-rw-r--r--packages/SystemUI/res/values-nb/strings.xml27
-rw-r--r--packages/SystemUI/res/values-ne/strings.xml29
-rw-r--r--packages/SystemUI/res/values-nl/strings.xml31
-rw-r--r--packages/SystemUI/res/values-or/strings.xml29
-rw-r--r--packages/SystemUI/res/values-pa/strings.xml27
-rw-r--r--packages/SystemUI/res/values-pl/strings.xml27
-rw-r--r--packages/SystemUI/res/values-pt-rBR/strings.xml33
-rw-r--r--packages/SystemUI/res/values-pt-rPT/strings.xml29
-rw-r--r--packages/SystemUI/res/values-pt/strings.xml33
-rw-r--r--packages/SystemUI/res/values-ro/strings.xml27
-rw-r--r--packages/SystemUI/res/values-ru/strings.xml29
-rw-r--r--packages/SystemUI/res/values-si/strings.xml27
-rw-r--r--packages/SystemUI/res/values-sk/strings.xml27
-rw-r--r--packages/SystemUI/res/values-sl/strings.xml29
-rw-r--r--packages/SystemUI/res/values-sq/strings.xml27
-rw-r--r--packages/SystemUI/res/values-sr/strings.xml27
-rw-r--r--packages/SystemUI/res/values-sv/strings.xml27
-rw-r--r--packages/SystemUI/res/values-sw/strings.xml27
-rw-r--r--packages/SystemUI/res/values-ta/strings.xml27
-rw-r--r--packages/SystemUI/res/values-te/strings.xml29
-rw-r--r--packages/SystemUI/res/values-th/strings.xml37
-rw-r--r--packages/SystemUI/res/values-tl/strings.xml27
-rw-r--r--packages/SystemUI/res/values-tr/strings.xml27
-rw-r--r--packages/SystemUI/res/values-uk/strings.xml27
-rw-r--r--packages/SystemUI/res/values-ur/strings.xml27
-rw-r--r--packages/SystemUI/res/values-uz/strings.xml27
-rw-r--r--packages/SystemUI/res/values-vi/strings.xml27
-rw-r--r--packages/SystemUI/res/values-zh-rCN/strings.xml27
-rw-r--r--packages/SystemUI/res/values-zh-rHK/strings.xml29
-rw-r--r--packages/SystemUI/res/values-zh-rTW/strings.xml29
-rw-r--r--packages/SystemUI/res/values-zu/strings.xml27
-rw-r--r--packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java30
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java265
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java275
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java15
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java12
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java55
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java196
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java73
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java56
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java40
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java244
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java275
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java350
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java349
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java147
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java113
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java66
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java563
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java379
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java19
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java30
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewController.java58
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java107
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java148
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java324
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java350
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java394
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java413
-rw-r--r--packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java9
-rw-r--r--packages/SystemUI/src/com/android/keyguard/NumPadKey.java4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/Dependency.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIFactory.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java110
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeLog.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/Lifecycle.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt (renamed from packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutDialogReceiver.kt)4
-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/gestural/BackGestureTfClassifierProvider.java66
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java138
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java75
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipController.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsViewController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/tv/dagger/TvPipModule.java92
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java87
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java46
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java52
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java56
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/ViewController.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java51
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java23
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java122
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java87
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java55
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt84
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewTest.kt59
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java106
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewTest.java79
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java130
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java62
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java102
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java81
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java246
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipControllerTest.java115
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTaskOrganizerTest.java93
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java141
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java117
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java43
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java30
-rw-r--r--packages/Tethering/apex/Android.bp1
-rw-r--r--packages/Tethering/bpf_progs/Android.bp33
-rw-r--r--packages/Tethering/bpf_progs/offload.c207
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java6
-rw-r--r--services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java23
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java36
-rw-r--r--services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java114
-rw-r--r--services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java189
-rw-r--r--services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java22
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java5
-rw-r--r--services/core/java/com/android/server/BluetoothManagerService.java3
-rw-r--r--services/core/java/com/android/server/TelephonyRegistry.java6
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java6
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java5
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java14
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java16
-rw-r--r--services/core/java/com/android/server/am/CachedAppOptimizer.java55
-rw-r--r--services/core/java/com/android/server/am/UserController.java22
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java88
-rw-r--r--services/core/java/com/android/server/appop/TEST_MAPPING3
-rw-r--r--services/core/java/com/android/server/connectivity/Vpn.java114
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceConfig.java74
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java35
-rw-r--r--services/core/java/com/android/server/display/DisplayModeDirector.java60
-rw-r--r--services/core/java/com/android/server/display/LocalDisplayAdapter.java9
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java192
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecController.java71
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java15
-rw-r--r--services/core/java/com/android/server/location/AbstractLocationProvider.java13
-rw-r--r--services/core/java/com/android/server/location/LocationManagerService.java10
-rw-r--r--services/core/java/com/android/server/location/LocationProviderManager.java256
-rw-r--r--services/core/java/com/android/server/location/PassiveLocationProviderManager.java2
-rw-r--r--services/core/java/com/android/server/location/geofence/GeofenceManager.java15
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java4
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java40
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java5
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java5
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssStatusProvider.java5
-rw-r--r--services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java25
-rw-r--r--services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java156
-rw-r--r--services/core/java/com/android/server/location/listeners/ListenerRegistration.java60
-rw-r--r--services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java24
-rw-r--r--services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java17
-rw-r--r--services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java29
-rw-r--r--services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java58
-rw-r--r--services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java214
-rw-r--r--services/core/java/com/android/server/location/timezone/ControllerCallbackImpl.java43
-rw-r--r--services/core/java/com/android/server/location/timezone/ControllerEnvironmentImpl.java51
-rw-r--r--services/core/java/com/android/server/location/timezone/ControllerImpl.java420
-rw-r--r--services/core/java/com/android/server/location/timezone/HandlerThreadingDomain.java73
-rw-r--r--services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java269
-rw-r--r--services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerShellCommand.java80
-rw-r--r--services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java511
-rw-r--r--services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java124
-rw-r--r--services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderProxy.java119
-rw-r--r--services/core/java/com/android/server/location/timezone/NullLocationTimeZoneProvider.java97
-rw-r--r--services/core/java/com/android/server/location/timezone/SimulatedBinderProviderEvent.java162
-rw-r--r--services/core/java/com/android/server/location/timezone/SimulatedLocationTimeZoneProviderProxy.java118
-rw-r--r--services/core/java/com/android/server/location/timezone/ThreadingDomain.java120
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsService.java10
-rw-r--r--services/core/java/com/android/server/pm/ApkChecksums.java28
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java65
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java130
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java14
-rw-r--r--services/core/java/com/android/server/pm/StagingManager.java12
-rw-r--r--services/core/java/com/android/server/pm/TEST_MAPPING9
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java6
-rw-r--r--services/core/java/com/android/server/pm/permission/BasePermission.java9
-rw-r--r--services/core/java/com/android/server/pm/permission/DevicePermissionState.java77
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerService.java1187
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java8
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionState.java129
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionsState.java22
-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.java574
-rw-r--r--services/core/java/com/android/server/pm/permission/UserPermissionState.java103
-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.java348
-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/timezonedetector/TimeZoneDetectorService.java8
-rw-r--r--services/core/java/com/android/server/vr/Vr2dDisplay.java1
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java14
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java47
-rw-r--r--services/core/java/com/android/server/wm/BarController.java340
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java68
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java618
-rw-r--r--services/core/java/com/android/server/wm/EventLogTags.logtags2
-rw-r--r--services/core/java/com/android/server/wm/InsetsPolicy.java38
-rw-r--r--services/core/java/com/android/server/wm/InsetsSourceProvider.java21
-rw-r--r--services/core/java/com/android/server/wm/InsetsStateController.java5
-rw-r--r--services/core/java/com/android/server/wm/StatusBarController.java109
-rw-r--r--services/core/java/com/android/server/wm/Task.java13
-rw-r--r--services/core/java/com/android/server/wm/TaskDisplayArea.java32
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotController.java6
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotSurface.java54
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java3
-rw-r--r--services/core/java/com/android/server/wm/WindowProcessController.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java4
-rw-r--r--services/core/jni/com_android_server_am_CachedAppOptimizer.cpp30
-rw-r--r--services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp31
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java450
-rw-r--r--services/incremental/BinderIncrementalService.cpp12
-rw-r--r--services/incremental/BinderIncrementalService.h4
-rw-r--r--services/incremental/IncrementalService.cpp106
-rw-r--r--services/incremental/IncrementalService.h7
-rw-r--r--services/java/com/android/server/SystemServer.java14
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java151
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java5
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java26
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java21
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java38
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java191
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java160
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java11
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java528
-rw-r--r--services/tests/servicestests/src/com/android/server/location/timezone/HandlerThreadingDomainTest.java208
-rw-r--r--services/tests/servicestests/src/com/android/server/location/timezone/NullLocationTimeZoneProviderTest.java134
-rw-r--r--services/tests/servicestests/src/com/android/server/location/timezone/TestSupport.java53
-rw-r--r--services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java128
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java18
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/TestState.java23
-rw-r--r--services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java32
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java67
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java48
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java18
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java35
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java39
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java12
-rw-r--r--services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java18
-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.java267
-rw-r--r--telephony/api/system-current.txt33
-rw-r--r--telephony/java/android/telephony/CallForwardingInfo.java105
-rwxr-xr-xtelephony/java/android/telephony/CarrierConfigManager.java46
-rw-r--r--telephony/java/android/telephony/CellIdentityNr.java6
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java317
-rw-r--r--telephony/java/android/telephony/gsm/GsmCellLocation.java2
-rw-r--r--telephony/java/android/telephony/ims/ImsService.java76
-rw-r--r--telephony/java/android/telephony/ims/aidl/IImsServiceController.aidl8
-rw-r--r--telephony/java/android/telephony/ims/compat/ImsService.java87
-rw-r--r--telephony/java/com/android/ims/internal/IImsServiceController.aidl10
-rw-r--r--telephony/java/com/android/internal/telephony/ICallForwardingInfoCallback.aidl25
-rw-r--r--telephony/java/com/android/internal/telephony/ITelephony.aidl76
-rw-r--r--tests/FlickerTests/Android.bp6
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/SplitScreenToLauncherTest.kt2
-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--wifi/api/system-current.txt2
-rw-r--r--wifi/java/android/net/wifi/ScanResult.java21
-rw-r--r--wifi/java/android/net/wifi/SoftApInfo.java39
-rw-r--r--wifi/java/android/net/wifi/WifiConfiguration.java26
-rw-r--r--wifi/tests/src/android/net/wifi/ScanResultTest.java7
-rw-r--r--wifi/tests/src/android/net/wifi/SoftApInfoTest.java18
690 files changed, 23660 insertions, 10397 deletions
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..d97b5003b252 100644
--- a/apct-tests/perftests/autofill/src/android/view/autofill/AbstractAutofillPerfTestCase.java
+++ b/apct-tests/perftests/autofill/src/android/view/autofill/AbstractAutofillPerfTestCase.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,10 +28,10 @@ import android.provider.Settings;
import androidx.test.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
-import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
+import org.junit.rules.RuleChain;
/**
* Base class for all autofill tests.
@@ -43,12 +43,16 @@ public abstract class AbstractAutofillPerfTestCase {
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;
@@ -72,20 +76,6 @@ 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.
*/
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..f1f812ddf1c7
--- /dev/null
+++ b/apct-tests/perftests/autofill/src/android/view/autofill/AutofillTestWatcher.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 android.view.autofill;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+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);
+
+ enableVerboseLog();
+ MyAutofillService.resetStaticState();
+ MyAutofillService.setEnabled(true);
+ setServiceWatcher();
+ }
+
+ @Override
+ protected void finished(Description description) {
+ super.finished(description);
+
+ restoreLogLevel();
+ disableService();
+ clearServiceWatcher();
+ }
+
+ void waitServiceConnect() throws InterruptedException {
+ if (sServiceWatcher != null) {
+ Log.d(TAG, "waitServiceConnect()");
+ sServiceWatcher.waitOnConnected();
+ }
+ }
+
+ private void enableService() {
+ MyAutofillService.resetStaticState();
+ MyAutofillService.setEnabled(true);
+ }
+
+ private 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);
+ }
+
+ 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.w(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..99b2590e4049 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;
@@ -57,13 +57,13 @@ public class LoginTest extends AbstractAutofillPerfTestCase {
public void testFocus_noService() throws Throwable {
resetService();
- mActivityRule.runOnUiThread(() -> {
- BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- while (state.keepRunning()) {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ mActivityRule.runOnUiThread(() -> {
mUsername.requestFocus();
mPassword.requestFocus();
- }
- });
+ });
+ }
}
/**
@@ -82,13 +82,13 @@ public class LoginTest extends AbstractAutofillPerfTestCase {
// 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();
- }
- });
+ });
+ }
}
/**
@@ -252,18 +252,18 @@ 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
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..77c1b85b10ab 100644
--- a/apct-tests/perftests/autofill/src/android/view/autofill/MyAutofillService.java
+++ b/apct-tests/perftests/autofill/src/android/view/autofill/MyAutofillService.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,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 +41,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 +53,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 +98,11 @@ public class MyAutofillService extends AutofillService {
}
@Override
+ public void onConnected() {
+ AutofillTestWatcher.ServiceWatcher.onConnected();
+ }
+
+ @Override
public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
FillCallback callback) {
try {
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index 42725c51fd87..bf5781beb52b 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -101,10 +101,15 @@ public abstract class JobScheduler {
* version {@link android.os.Build.VERSION_CODES#Q}. As such, the system may throttle calls to
* this API if calls are made too frequently in a short amount of time.
*
+ * <p>Note: The JobService component needs to be enabled in order to successfully schedule a
+ * job.
+ *
* @param job The job you wish scheduled. See
* {@link android.app.job.JobInfo.Builder JobInfo.Builder} for more detail on the sorts of jobs
* you can schedule.
* @return the result of the schedule request.
+ * @throws IllegalArgumentException if the specified {@link JobService} doesn't exist or is
+ * disabled.
*/
public abstract @Result int schedule(@NonNull JobInfo job);
@@ -137,11 +142,21 @@ public abstract class JobScheduler {
* work you are enqueue, since currently this will always be treated as a different JobInfo,
* even if the ClipData contents are exactly the same.</p>
*
+ * <p class="caution"><strong>Note:</strong> Scheduling a job can have a high cost, even if it's
+ * rescheduling the same job and the job didn't execute, especially on platform versions before
+ * version {@link android.os.Build.VERSION_CODES#Q}. As such, the system may throttle calls to
+ * this API if calls are made too frequently in a short amount of time.
+ *
+ * <p>Note: The JobService component needs to be enabled in order to successfully schedule a
+ * job.
+ *
* @param job The job you wish to enqueue work for. See
* {@link android.app.job.JobInfo.Builder JobInfo.Builder} for more detail on the sorts of jobs
* you can schedule.
* @param work New work to enqueue. This will be available later when the job starts running.
* @return the result of the enqueue request.
+ * @throws IllegalArgumentException if the specified {@link JobService} doesn't exist or is
+ * disabled.
*/
public abstract @Result int enqueue(@NonNull JobInfo job, @NonNull JobWorkItem work);
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 ec7e99e78cb3..05910a5b171e 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,7 @@ import static android.app.AlarmManager.RTC_WAKEUP;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.os.UserHandle.USER_SYSTEM;
+import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.Activity;
import android.app.ActivityManagerInternal;
@@ -39,12 +40,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 +67,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 +76,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 +101,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;
@@ -252,7 +252,7 @@ public class AlarmManagerService extends SystemService {
final ArrayList<IdleDispatchEntry> mAllowWhileIdleDispatches = new ArrayList();
interface Stats {
- int REORDER_ALARMS_FOR_STANDBY = 1;
+ int REORDER_ALARMS_FOR_STANDBY = 0;
}
private final StatLogger mStatLogger = new StatLogger("Alarm manager stats", new String[]{
@@ -374,7 +374,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 +394,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 +460,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 +484,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) {
@@ -1199,7 +1243,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 +1328,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);
@@ -2722,6 +2766,14 @@ public class AlarmManagerService extends SystemService {
mPendingBackgroundAlarms.removeAt(i);
}
}
+ for (int i = mPendingNonWakeupAlarms.size() - 1; i >= 0; i--) {
+ final Alarm a = mPendingNonWakeupAlarms.get(i);
+ if (a.matches(operation, directReceiver)) {
+ // Don't set didRemove, since this doesn't impact the scheduled alarms.
+ mPendingNonWakeupAlarms.remove(i);
+ decrementAlarmCount(a.uid, 1);
+ }
+ }
if (didRemove) {
if (DEBUG_BATCH) {
Slog.v(TAG, "remove(operation) changed bounds; rebatching");
@@ -3374,6 +3426,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 {
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 a08c22213c1f..91c0c0579c69 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/BatchingAlarmStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/BatchingAlarmStore.java
@@ -46,7 +46,7 @@ public class BatchingAlarmStore implements AlarmStore {
private AlarmClockRemovalListener mAlarmClockRemovalListener;
interface Stats {
- int REBATCH_ALL_ALARMS = 1;
+ int REBATCH_ALL_ALARMS = 0;
}
final StatLogger mStatLogger = new StatLogger("Alarm store stats", new String[]{
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 cf4caea9c487..b638fef36a9e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -87,11 +87,11 @@ import com.android.server.AppStateTrackerImpl;
import com.android.server.DeviceIdleInternal;
import com.android.server.JobSchedulerBackgroundThread;
import com.android.server.LocalServices;
-import com.android.server.SystemService.TargetUser;
import com.android.server.job.JobSchedulerServiceDumpProto.ActiveJob;
import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob;
import com.android.server.job.controllers.BackgroundJobsController;
import com.android.server.job.controllers.BatteryController;
+import com.android.server.job.controllers.ComponentController;
import com.android.server.job.controllers.ConnectivityController;
import com.android.server.job.controllers.ContentObserverController;
import com.android.server.job.controllers.DeviceIdleJobsController;
@@ -1484,6 +1484,7 @@ public class JobSchedulerService extends com.android.server.SystemService
mControllers.add(mDeviceIdleJobsController);
mQuotaController = new QuotaController(this);
mControllers.add(mQuotaController);
+ mControllers.add(new ComponentController(this));
mRestrictiveControllers = new ArrayList<>();
mRestrictiveControllers.add(mBatteryController);
@@ -2300,21 +2301,12 @@ public class JobSchedulerService extends com.android.server.SystemService
return false;
}
- // The expensive check: validate that the defined package+service is
- // still present & viable.
+ // Validate that the defined package+service is still present & viable.
return isComponentUsable(job);
}
private boolean isComponentUsable(@NonNull JobStatus job) {
- final ServiceInfo service;
- try {
- // TODO: cache result until we're notified that something in the package changed.
- service = AppGlobals.getPackageManager().getServiceInfo(
- job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
- job.getUserId());
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
+ final ServiceInfo service = job.serviceInfo;
if (service == null) {
if (DEBUG) {
@@ -3104,7 +3096,7 @@ public class JobSchedulerService extends com.android.server.SystemService
try {
componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
js.getServiceComponent(),
- PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
+ PackageManager.MATCH_DIRECT_BOOT_AUTO,
js.getUserId()) != null);
} catch (RemoteException e) {
}
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
new file mode 100644
index 000000000000..47ebf32fa875
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.job.controllers;
+
+import 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.ServiceInfo;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArrayMap;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.server.job.JobSchedulerService;
+
+import java.util.Objects;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+/**
+ * Controller that tracks changes in the service component's enabled state.
+ */
+public class ComponentController extends StateController {
+ private static final String TAG = "JobScheduler.Component";
+ private static final boolean DEBUG = JobSchedulerService.DEBUG
+ || Log.isLoggable(TAG, Log.DEBUG);
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (action == null) {
+ Slog.wtf(TAG, "Intent action was null");
+ return;
+ }
+ switch (action) {
+ case Intent.ACTION_PACKAGE_CHANGED:
+ final Uri uri = intent.getData();
+ final String pkg = uri != null ? uri.getSchemeSpecificPart() : null;
+ final String[] changedComponents = intent.getStringArrayExtra(
+ Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
+ if (pkg != null && changedComponents != null && changedComponents.length > 0) {
+ updateComponentStateForPackage(pkg);
+ }
+ break;
+ case Intent.ACTION_USER_UNLOCKED:
+ case Intent.ACTION_USER_STOPPED:
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
+ updateComponentStateForUser(userId);
+ break;
+ }
+ }
+ };
+
+ private final SparseArrayMap<ComponentName, ServiceInfo> mServiceInfoCache =
+ new SparseArrayMap<>();
+
+ private final ComponentStateUpdateFunctor mComponentStateUpdateFunctor =
+ new ComponentStateUpdateFunctor();
+
+ public ComponentController(JobSchedulerService service) {
+ super(service);
+
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addDataScheme("package");
+ mContext.registerReceiverAsUser(
+ mBroadcastReceiver, UserHandle.ALL, filter, null, null);
+ final IntentFilter userFilter = new IntentFilter();
+ userFilter.addAction(Intent.ACTION_USER_UNLOCKED);
+ userFilter.addAction(Intent.ACTION_USER_STOPPED);
+ mContext.registerReceiverAsUser(
+ mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
+
+ }
+
+ @Override
+ public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
+ updateComponentEnabledStateLocked(jobStatus);
+ }
+
+ @Override
+ public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
+ boolean forUpdate) {
+ }
+
+ @Nullable
+ private ServiceInfo getServiceInfo(JobStatus jobStatus) {
+ final ComponentName service = jobStatus.getServiceComponent();
+ final int userId = jobStatus.getUserId();
+ 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);
+ }
+ mServiceInfoCache.add(userId, service, si);
+ }
+ return si;
+ }
+
+ private boolean updateComponentEnabledStateLocked(JobStatus jobStatus) {
+ final ServiceInfo service = getServiceInfo(jobStatus);
+
+ if (DEBUG && service == null) {
+ Slog.v(TAG, jobStatus.toShortString() + " component not present");
+ }
+ final ServiceInfo ogService = jobStatus.serviceInfo;
+ jobStatus.serviceInfo = service;
+ return !Objects.equals(ogService, service);
+ }
+
+ private void updateComponentStateForPackage(final String pkg) {
+ synchronized (mLock) {
+ for (int u = mServiceInfoCache.numMaps() - 1; u >= 0; --u) {
+ final int userId = mServiceInfoCache.keyAt(u);
+
+ for (int c = mServiceInfoCache.numElementsForKey(userId) - 1; c >= 0; --c) {
+ final ComponentName cn = mServiceInfoCache.keyAt(u, c);
+ if (cn.getPackageName().equals(pkg)) {
+ mServiceInfoCache.delete(userId, cn);
+ }
+ }
+ }
+ updateComponentStatesLocked(
+ jobStatus -> jobStatus.getServiceComponent().getPackageName().equals(pkg));
+ }
+ }
+
+ private void updateComponentStateForUser(final int userId) {
+ synchronized (mLock) {
+ mServiceInfoCache.delete(userId);
+ updateComponentStatesLocked(jobStatus -> {
+ // Using user ID instead of source user ID because the service will run under the
+ // user ID, not source user ID.
+ return jobStatus.getUserId() == userId;
+ });
+ }
+ }
+
+ private void updateComponentStatesLocked(@NonNull Predicate<JobStatus> filter) {
+ mComponentStateUpdateFunctor.reset();
+ mService.getJobStore().forEachJob(filter, mComponentStateUpdateFunctor);
+ if (mComponentStateUpdateFunctor.mChanged) {
+ mStateChangedListener.onControllerStateChanged();
+ }
+ }
+
+ final class ComponentStateUpdateFunctor implements Consumer<JobStatus> {
+ boolean mChanged;
+
+ @Override
+ public void accept(JobStatus jobStatus) {
+ mChanged |= updateComponentEnabledStateLocked(jobStatus);
+ }
+
+ private void reset() {
+ mChanged = false;
+ }
+ }
+
+ @Override
+ public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
+
+ }
+
+ @Override
+ public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
+ Predicate<JobStatus> predicate) {
+
+ }
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index d7be2595e88b..c7cc2f03b062 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -27,6 +27,7 @@ import android.app.job.JobInfo;
import android.app.job.JobWorkItem;
import android.content.ClipData;
import android.content.ComponentName;
+import android.content.pm.ServiceInfo;
import android.net.Network;
import android.net.Uri;
import android.os.RemoteException;
@@ -296,6 +297,7 @@ public final class JobStatus {
public ArraySet<Uri> changedUris;
public ArraySet<String> changedAuthorities;
public Network network;
+ public ServiceInfo serviceInfo;
public int lastEvaluatedPriority;
@@ -1284,8 +1286,8 @@ public final class JobStatus {
// run if its constraints are satisfied).
// DeviceNotDozing implicit constraint must be satisfied
// NotRestrictedInBackground implicit constraint must be satisfied
- return mReadyNotDozing && mReadyNotRestrictedInBg && (mReadyDeadlineSatisfied
- || isConstraintsSatisfied(satisfiedConstraints));
+ return mReadyNotDozing && mReadyNotRestrictedInBg && (serviceInfo != null)
+ && (mReadyDeadlineSatisfied || isConstraintsSatisfied(satisfiedConstraints));
}
/** All constraints besides implicit and deadline. */
@@ -1767,6 +1769,9 @@ public final class JobStatus {
pw.print(prefix);
pw.print(" readyDynamicSatisfied: ");
pw.println(mReadyDynamicSatisfied);
+ pw.print(prefix);
+ pw.print(" readyComponentEnabled: ");
+ pw.println(serviceInfo != null);
if (changedAuthorities != null) {
pw.print(prefix); pw.println("Changed authorities:");
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index c033138d5f20..22e1eec8883d 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -1159,6 +1159,10 @@ public class AppStandbyController implements AppStandbyInternal {
if (isDeviceProvisioningPackage(packageName)) {
return STANDBY_BUCKET_EXEMPTED;
}
+
+ if (mInjector.isWellbeingPackage(packageName)) {
+ return STANDBY_BUCKET_WORKING_SET;
+ }
}
// Check this last, as it can be the most expensive check
@@ -1966,6 +1970,7 @@ public class AppStandbyController implements AppStandbyInternal {
*/
@GuardedBy("mPowerWhitelistedApps")
private final ArraySet<String> mPowerWhitelistedApps = new ArraySet<>();
+ private String mWellbeingApp = null;
Injector(Context context, Looper looper) {
mContext = context;
@@ -1999,6 +2004,9 @@ public class AppStandbyController implements AppStandbyInternal {
if (activityManager.isLowRamDevice() || ActivityManager.isSmallBatteryDevice()) {
mAutoRestrictedBucketDelayMs = 12 * ONE_HOUR;
}
+
+ final PackageManager packageManager = mContext.getPackageManager();
+ mWellbeingApp = packageManager.getWellbeingPackageName();
}
mBootPhase = phase;
}
@@ -2043,6 +2051,14 @@ public class AppStandbyController implements AppStandbyInternal {
}
}
+ /**
+ * Returns {@code true} if the supplied package is the wellbeing app. Otherwise,
+ * returns {@code false}.
+ */
+ boolean isWellbeingPackage(String packageName) {
+ return mWellbeingApp != null && mWellbeingApp.equals(packageName);
+ }
+
void updatePowerWhitelistCache() {
try {
// Don't call out to DeviceIdleController with the lock held.
diff --git a/api/current.txt b/api/current.txt
index 922e89c3efd9..4590325cb771 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -11359,6 +11359,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
@@ -11451,6 +11452,7 @@ package android.content.pm {
method public int describeContents();
method public int getKind();
method @Nullable public java.security.cert.Certificate getSourceCertificate() throws java.security.cert.CertificateException;
+ method @Nullable public String getSourcePackageName();
method @Nullable public String getSplitName();
method @NonNull public byte[] getValue();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -11559,6 +11561,7 @@ 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 @NonNull public byte[] getValue();
@@ -11566,9 +11569,9 @@ package android.content.pm {
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 @Deprecated 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 @Deprecated 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
}
@@ -11864,7 +11867,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);
@@ -12061,6 +12064,7 @@ package android.content.pm {
method public boolean getSyntheticAppDetailsActivityEnabled(@NonNull String);
method @NonNull public abstract android.content.pm.FeatureInfo[] getSystemAvailableFeatures();
method @Nullable public abstract String[] getSystemSharedLibraryNames();
+ method @IntRange(from=0) public int getTargetSdkVersion(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method @Nullable public abstract CharSequence getText(@NonNull String, @StringRes int, @Nullable android.content.pm.ApplicationInfo);
method @NonNull public abstract android.graphics.drawable.Drawable getUserBadgedDrawableForDensity(@NonNull android.graphics.drawable.Drawable, @NonNull android.os.UserHandle, @Nullable android.graphics.Rect, int);
method @NonNull public abstract android.graphics.drawable.Drawable getUserBadgedIcon(@NonNull android.graphics.drawable.Drawable, @NonNull android.os.UserHandle);
@@ -12679,6 +12683,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
@@ -12745,6 +12752,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;
@@ -24886,6 +24894,7 @@ package android.media {
}
public static final class DrmInitData.SchemeInitData {
+ ctor public DrmInitData.SchemeInitData(@NonNull java.util.UUID, @NonNull String, @NonNull byte[]);
field @NonNull public static final java.util.UUID UUID_NIL;
field public final byte[] data;
field public final String mimeType;
@@ -27325,6 +27334,12 @@ package android.media {
field public static final android.media.MediaTimestamp TIMESTAMP_UNKNOWN;
}
+ public class MediaTranscodingException extends java.lang.Exception {
+ }
+
+ public static final class MediaTranscodingException.ServiceNotAvailableException extends android.media.MediaTranscodingException {
+ }
+
public interface MicrophoneDirection {
method public boolean setPreferredMicrophoneDirection(int);
method public boolean setPreferredMicrophoneFieldDimension(@FloatRange(from=-1.0, to=1.0) float);
@@ -44279,54 +44294,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);
@@ -46760,6 +46729,7 @@ package android.telephony {
field public static final String KEY_APN_EXPAND_BOOL = "apn_expand_bool";
field public static final String KEY_APN_SETTINGS_DEFAULT_APN_TYPES_STRING_ARRAY = "apn_settings_default_apn_types_string_array";
field public static final String KEY_AUTO_RETRY_ENABLED_BOOL = "auto_retry_enabled_bool";
+ field public static final String KEY_CALL_BARRING_DEFAULT_SERVICE_CLASS_INT = "call_barring_default_service_class_int";
field public static final String KEY_CALL_BARRING_SUPPORTS_DEACTIVATE_ALL_BOOL = "call_barring_supports_deactivate_all_bool";
field public static final String KEY_CALL_BARRING_SUPPORTS_PASSWORD_CHANGE_BOOL = "call_barring_supports_password_change_bool";
field public static final String KEY_CALL_BARRING_VISIBILITY_BOOL = "call_barring_visibility_bool";
@@ -46987,6 +46957,8 @@ package android.telephony {
field public static final String KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING = "wfc_emergency_address_carrier_app_string";
field public static final String KEY_WORLD_MODE_ENABLED_BOOL = "world_mode_enabled_bool";
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
}
public static final class CarrierConfigManager.Apn {
diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt
index 4f0e2cd3c768..d27f5a5c006e 100644
--- a/api/module-lib-current.txt
+++ b/api/module-lib-current.txt
@@ -10,6 +10,10 @@ package android.app {
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);
+ }
+
}
package android.content.rollback {
@@ -62,6 +66,7 @@ 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 void dispatchVolumeKeyEventAsSystemService(@NonNull android.view.KeyEvent, int);
@@ -149,6 +154,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 b8fa299b01eb..c4f9067b507d 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1592,12 +1592,16 @@ package android.bluetooth {
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void setBluetoothTethering(boolean);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED";
+ field public static final String ACTION_TETHERING_STATE_CHANGED = "android.bluetooth.action.TETHERING_STATE_CHANGED";
field public static final String EXTRA_LOCAL_ROLE = "android.bluetooth.pan.extra.LOCAL_ROLE";
+ field public static final String EXTRA_TETHERING_STATE = "android.bluetooth.extra.TETHERING_STATE";
field public static final int LOCAL_NAP_ROLE = 1; // 0x1
field public static final int LOCAL_PANU_ROLE = 2; // 0x2
field public static final int PAN_ROLE_NONE = 0; // 0x0
field public static final int REMOTE_NAP_ROLE = 1; // 0x1
field public static final int REMOTE_PANU_ROLE = 2; // 0x2
+ field public static final int TETHERING_STATE_OFF = 1; // 0x1
+ field public static final int TETHERING_STATE_ON = 2; // 0x2
}
public class BluetoothPbap implements android.bluetooth.BluetoothProfile {
@@ -4363,7 +4367,7 @@ package android.media {
}
public final class MediaTranscodeManager {
- method @NonNull public android.media.MediaTranscodeManager.TranscodingJob enqueueRequest(@NonNull android.media.MediaTranscodeManager.TranscodingRequest, @NonNull java.util.concurrent.Executor, @NonNull android.media.MediaTranscodeManager.OnTranscodingFinishedListener) throws java.io.FileNotFoundException;
+ method @NonNull public android.media.MediaTranscodeManager.TranscodingJob enqueueRequest(@NonNull android.media.MediaTranscodeManager.TranscodingRequest, @NonNull java.util.concurrent.Executor, @NonNull android.media.MediaTranscodeManager.OnTranscodingFinishedListener) throws java.io.FileNotFoundException, android.media.MediaTranscodingException.ServiceNotAvailableException;
field public static final int PRIORITY_REALTIME = 1; // 0x1
field public static final int TRANSCODING_TYPE_VIDEO = 1; // 0x1
}
@@ -4378,7 +4382,6 @@ package android.media {
method @IntRange(from=0, to=100) public int getProgress();
method public int getResult();
method public int getStatus();
- method public boolean retry();
method public void setOnProgressUpdateListener(@NonNull java.util.concurrent.Executor, @Nullable android.media.MediaTranscodeManager.TranscodingJob.OnProgressUpdateListener);
method public void setOnProgressUpdateListener(int, @NonNull java.util.concurrent.Executor, @Nullable android.media.MediaTranscodeManager.TranscodingJob.OnProgressUpdateListener);
field public static final int RESULT_CANCELED = 4; // 0x4
@@ -7205,6 +7208,7 @@ package android.net.wifi {
method public int getBandwidth();
method @Nullable public android.net.MacAddress getBssid();
method public int getFrequency();
+ method public int getWifiStandard();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field public static final int CHANNEL_WIDTH_160MHZ = 6; // 0x6
field public static final int CHANNEL_WIDTH_20MHZ = 2; // 0x2
@@ -7244,6 +7248,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;
@@ -9374,6 +9379,7 @@ package android.provider {
field public static final String APN_SET_ID = "apn_set_id";
field public static final int CARRIER_EDITED = 4; // 0x4
field public static final String EDITED_STATUS = "edited";
+ field public static final int MATCH_ALL_APN_SET_ID = -1; // 0xffffffff
field public static final String MAX_CONNECTIONS = "max_conns";
field public static final String MODEM_PERSIST = "modem_cognitive";
field public static final String MTU = "mtu";
@@ -10267,7 +10273,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();
}
@@ -10658,6 +10710,26 @@ 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 ERROR_FDN_CHECK_FAILURE = 2; // 0x2
+ field public static final int ERROR_NOT_SUPPORTED = 3; // 0x3
+ field public static final int ERROR_UNKNOWN = 1; // 0x1
+ 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
+ field public static final int SUCCESS = 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);
@@ -10931,7 +11003,8 @@ package android.telephony {
method public void onCallAttributesChanged(@NonNull android.telephony.CallAttributes);
method @Deprecated public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber);
method public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber, int);
- method public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber);
+ method @Deprecated public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber);
+ method public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber, int);
method public void onPhysicalChannelConfigurationChanged(@NonNull java.util.List<android.telephony.PhysicalChannelConfig>);
method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onPreciseCallStateChanged(@NonNull android.telephony.PreciseCallState);
method public void onRadioPowerStateChanged(int);
@@ -11318,6 +11391,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);
@@ -11394,6 +11469,8 @@ 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 setCallWaitingStatus(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);
@@ -11431,6 +11508,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
@@ -11485,6 +11566,11 @@ 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);
+ }
+
public final class UiccAccessRule implements android.os.Parcelable {
ctor public UiccAccessRule(byte[], @Nullable String, long);
method public int describeContents();
diff --git a/api/system-lint-baseline.txt b/api/system-lint-baseline.txt
index 9c40b6cf63ce..3a9f49c316ea 100644
--- a/api/system-lint-baseline.txt
+++ b/api/system-lint-baseline.txt
@@ -1,43 +1,48 @@
// Baseline format: 1.0
AcronymName: android.net.NetworkCapabilities#setSSID(String):
- Acronyms should not be capitalized in method names: was `setSSID`, should this be `setSsid`?
+
ActionValue: android.location.Location#EXTRA_NO_GPS_LOCATION:
-ActionValue: android.net.wifi.WifiManager#ACTION_LINK_CONFIGURATION_CHANGED:
-
-
-// Tethering broadcast action / extras cannot change name for backwards compatibility
ActionValue: android.net.TetheringManager#ACTION_TETHER_STATE_CHANGED:
- Inconsistent action value; expected `android.net.action.TETHER_STATE_CHANGED`, was `android.net.conn.TETHER_STATE_CHANGED`
+
ActionValue: android.net.TetheringManager#EXTRA_ACTIVE_TETHER:
- Inconsistent extra value; expected `android.net.extra.ACTIVE_TETHER`, was `tetherArray`
+
ActionValue: android.net.TetheringManager#EXTRA_AVAILABLE_TETHER:
- Inconsistent extra value; expected `android.net.extra.AVAILABLE_TETHER`, was `availableArray`
+
ActionValue: android.net.TetheringManager#EXTRA_ERRORED_TETHER:
- Inconsistent extra value; expected `android.net.extra.ERRORED_TETHER`, was `erroredArray`
+
+ActionValue: android.net.wifi.WifiManager#ACTION_LINK_CONFIGURATION_CHANGED:
+
+
ArrayReturn: android.bluetooth.BluetoothCodecStatus#BluetoothCodecStatus(android.bluetooth.BluetoothCodecConfig, android.bluetooth.BluetoothCodecConfig[], android.bluetooth.BluetoothCodecConfig[]) parameter #1:
- Method parameter should be Collection<BluetoothCodecConfig> (or subclass) instead of raw array; was `android.bluetooth.BluetoothCodecConfig[]`
+
ArrayReturn: android.bluetooth.BluetoothCodecStatus#BluetoothCodecStatus(android.bluetooth.BluetoothCodecConfig, android.bluetooth.BluetoothCodecConfig[], android.bluetooth.BluetoothCodecConfig[]) parameter #2:
- Method parameter should be Collection<BluetoothCodecConfig> (or subclass) instead of raw array; was `android.bluetooth.BluetoothCodecConfig[]`
+
ArrayReturn: android.bluetooth.BluetoothCodecStatus#getCodecsLocalCapabilities():
- Method should return Collection<BluetoothCodecConfig> (or subclass) instead of raw array; was `android.bluetooth.BluetoothCodecConfig[]`
+
ArrayReturn: android.bluetooth.BluetoothCodecStatus#getCodecsSelectableCapabilities():
- Method should return Collection<BluetoothCodecConfig> (or subclass) instead of raw array; was `android.bluetooth.BluetoothCodecConfig[]`
+
ArrayReturn: android.bluetooth.BluetoothUuid#containsAnyUuid(android.os.ParcelUuid[], android.os.ParcelUuid[]) parameter #0:
- Method parameter should be Collection<ParcelUuid> (or subclass) instead of raw array; was `android.os.ParcelUuid[]`
+
ArrayReturn: android.bluetooth.BluetoothUuid#containsAnyUuid(android.os.ParcelUuid[], android.os.ParcelUuid[]) parameter #1:
- Method parameter should be Collection<ParcelUuid> (or subclass) instead of raw array; was `android.os.ParcelUuid[]`
+
ArrayReturn: android.media.tv.tuner.Tuner.FilterCallback#onFilterEvent(android.media.tv.tuner.Tuner.Filter, android.media.tv.tuner.filter.FilterEvent[]) parameter #1:
- Method parameter should be Collection<FilterEvent> (or subclass) instead of raw array; was `android.media.tv.tuner.filter.FilterEvent[]`
+
ArrayReturn: android.net.NetworkScoreManager#requestScores(android.net.NetworkKey[]) parameter #0:
- Method parameter should be Collection<NetworkKey> (or subclass) instead of raw array; was `android.net.NetworkKey[]`
+
ArrayReturn: android.view.contentcapture.ViewNode#getAutofillOptions():
+BuilderSetStyle: android.net.IpSecTransform.Builder#buildTunnelModeTransform(java.net.InetAddress, android.net.IpSecManager.SecurityParameterIndex):
+ Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.net.IpSecTransform.Builder.buildTunnelModeTransform(java.net.InetAddress,android.net.IpSecManager.SecurityParameterIndex)
+
+
+ExecutorRegistration: android.media.MediaPlayer#setOnImsRxNoticeListener(android.media.MediaPlayer.OnImsRxNoticeListener, android.os.Handler):
+ Registration methods should have overload that accepts delivery Executor: `setOnImsRxNoticeListener`
ExecutorRegistration: android.net.wifi.p2p.WifiP2pManager#deletePersistentGroup(android.net.wifi.p2p.WifiP2pManager.Channel, int, android.net.wifi.p2p.WifiP2pManager.ActionListener):
ExecutorRegistration: android.net.wifi.p2p.WifiP2pManager#factoryReset(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener):
@@ -65,7 +70,7 @@ GenericException: android.service.autofill.augmented.FillWindow#finalize():
IntentBuilderName: android.content.Context#registerReceiverForAllUsers(android.content.BroadcastReceiver, android.content.IntentFilter, String, android.os.Handler):
- Methods creating an Intent should be named `create<Foo>Intent()`, was `registerReceiverForAllUsers`
+
KotlinKeyword: android.app.Notification#when:
@@ -73,7 +78,19 @@ KotlinKeyword: android.app.Notification#when:
KotlinOperator: android.telephony.CbGeoUtils.Geometry#contains(android.telephony.CbGeoUtils.LatLng):
- Method can be invoked as a "in" operator from Kotlin: `contains` (this is usually desirable; just make sure it makes sense for this type of object)
+
+
+
+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)
+MissingGetterMatchingBuilder: android.security.keystore.KeyGenParameterSpec.Builder#setUid(int):
+ android.security.keystore.KeyGenParameterSpec does not declare a `getUid()` method matching method android.security.keystore.KeyGenParameterSpec.Builder.setUid(int)
+MissingGetterMatchingBuilder: android.service.autofill.Dataset.Builder#setFieldInlinePresentation(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, java.util.regex.Pattern, android.service.autofill.InlinePresentation):
+ android.service.autofill.Dataset does not declare a `getFieldInlinePresentation()` method matching method android.service.autofill.Dataset.Builder.setFieldInlinePresentation(android.view.autofill.AutofillId,android.view.autofill.AutofillValue,java.util.regex.Pattern,android.service.autofill.InlinePresentation)
+MissingGetterMatchingBuilder: android.telecom.CallScreeningService.CallResponse.Builder#setShouldScreenCallViaAudioProcessing(boolean):
+ android.telecom.CallScreeningService.CallResponse does not declare a `shouldScreenCallViaAudioProcessing()` method matching method android.telecom.CallScreeningService.CallResponse.Builder.setShouldScreenCallViaAudioProcessing(boolean)
+MissingGetterMatchingBuilder: android.telephony.mbms.DownloadRequest.Builder#setServiceId(String):
+ android.telephony.mbms.DownloadRequest does not declare a `getServiceId()` method matching method android.telephony.mbms.DownloadRequest.Builder.setServiceId(String)
MissingNullability: android.hardware.soundtrigger.SoundTrigger.ModuleProperties#toString():
@@ -217,28 +234,27 @@ MutableBareField: android.net.wifi.WifiScanner.ScanSettings#type:
NoClone: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #0:
-
+
NoSettingsProvider: android.provider.Settings.Global#TETHER_OFFLOAD_DISABLED:
- New setting keys are not allowed. (Field: TETHER_OFFLOAD_DISABLED)
+
NoSettingsProvider: android.provider.Settings.Global#TETHER_SUPPORTED:
- New setting keys are not allowed. (Field: TETHER_SUPPORTED)
NotCloseable: android.bluetooth.BluetoothA2dpSink:
- Classes that release resources (finalize()) should implement AutoClosable and CloseGuard: class android.bluetooth.BluetoothA2dpSink
+
NotCloseable: android.bluetooth.BluetoothMap:
- Classes that release resources (finalize()) should implement AutoClosable and CloseGuard: class android.bluetooth.BluetoothMap
+
NotCloseable: android.bluetooth.BluetoothPan:
- Classes that release resources (finalize()) should implement AutoClosable and CloseGuard: class android.bluetooth.BluetoothPan
+
NotCloseable: android.bluetooth.BluetoothPbap:
- Classes that release resources (finalize()) should implement AutoClosable and CloseGuard: class android.bluetooth.BluetoothPbap
+
OnNameExpected: android.content.ContentProvider#checkUriPermission(android.net.Uri, int, int):
- If implemented by developer, should follow the on<Something> style; otherwise consider marking final
+
PairedRegistration: android.net.wifi.nl80211.WifiNl80211Manager#registerApCallback(String, java.util.concurrent.Executor, android.net.wifi.nl80211.WifiNl80211Manager.SoftApCallback):
@@ -300,7 +316,7 @@ SamShouldBeLast: android.app.AlarmManager#setWindow(int, long, long, String, and
SamShouldBeLast: android.app.WallpaperInfo#dump(android.util.Printer, String):
SamShouldBeLast: android.app.WallpaperManager#addOnColorsChangedListener(android.app.WallpaperManager.OnColorsChangedListener, android.os.Handler):
- SAM-compatible parameters (such as parameter 1, "listener", in android.app.WallpaperManager.addOnColorsChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+
SamShouldBeLast: android.app.admin.DevicePolicyManager#installSystemUpdate(android.content.ComponentName, android.net.Uri, java.util.concurrent.Executor, android.app.admin.DevicePolicyManager.InstallSystemUpdateCallback):
SamShouldBeLast: android.content.Context#bindIsolatedService(android.content.Intent, int, String, java.util.concurrent.Executor, android.content.ServiceConnection):
@@ -334,19 +350,19 @@ SamShouldBeLast: android.location.LocationManager#registerGnssNavigationMessageC
SamShouldBeLast: android.location.LocationManager#registerGnssStatusCallback(java.util.concurrent.Executor, android.location.GnssStatus.Callback):
SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(String, long, float, android.location.LocationListener, android.os.Looper):
- SAM-compatible parameters (such as parameter 4, "listener", in android.location.LocationManager.requestLocationUpdates) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+
SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(String, long, float, java.util.concurrent.Executor, android.location.LocationListener):
SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(android.location.LocationRequest, java.util.concurrent.Executor, android.location.LocationListener):
SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(long, float, android.location.Criteria, android.location.LocationListener, android.os.Looper):
- SAM-compatible parameters (such as parameter 4, "listener", in android.location.LocationManager.requestLocationUpdates) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+
SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(long, float, android.location.Criteria, java.util.concurrent.Executor, android.location.LocationListener):
SamShouldBeLast: android.location.LocationManager#requestSingleUpdate(String, android.location.LocationListener, android.os.Looper):
- SAM-compatible parameters (such as parameter 2, "listener", in android.location.LocationManager.requestSingleUpdate) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+
SamShouldBeLast: android.location.LocationManager#requestSingleUpdate(android.location.Criteria, android.location.LocationListener, android.os.Looper):
- SAM-compatible parameters (such as parameter 2, "listener", in android.location.LocationManager.requestSingleUpdate) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+
SamShouldBeLast: android.media.AudioFocusRequest.Builder#setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener, android.os.Handler):
SamShouldBeLast: android.media.AudioManager#requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, int, int):
@@ -359,6 +375,10 @@ SamShouldBeLast: android.media.AudioRecordingMonitor#registerAudioRecordingCallb
SamShouldBeLast: android.media.AudioRouting#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
+SamShouldBeLast: android.media.AudioTrack#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
+ SAM-compatible parameters (such as parameter 1, "listener", in android.media.AudioTrack.addOnRoutingChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.media.MediaPlayer#setOnImsRxNoticeListener(android.media.MediaPlayer.OnImsRxNoticeListener, android.os.Handler):
+ SAM-compatible parameters (such as parameter 1, "listener", in android.media.MediaPlayer.setOnImsRxNoticeListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
SamShouldBeLast: android.media.MediaRecorder#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
SamShouldBeLast: android.media.MediaRecorder#registerAudioRecordingCallback(java.util.concurrent.Executor, android.media.AudioManager.AudioRecordingCallback):
@@ -510,8 +530,8 @@ ServiceName: android.provider.DeviceConfig#NAMESPACE_PACKAGE_MANAGER_SERVICE:
UserHandle: android.companion.CompanionDeviceManager#isDeviceAssociated(String, android.net.MacAddress, android.os.UserHandle):
- When a method overload is needed to target a specific UserHandle, callers should be directed to use Context.createPackageContextAsUser() and re-obtain the relevant Manager, and no new API should be added
+
UserHandleName: android.telephony.CellBroadcastIntents#sendOrderedBroadcastForBackgroundReceivers(android.content.Context, android.os.UserHandle, android.content.Intent, String, String, android.content.BroadcastReceiver, android.os.Handler, int, String, android.os.Bundle):
- Method taking UserHandle should be named `doFooAsUser` or `queryFooForUser`, was `sendOrderedBroadcastForBackgroundReceivers`
+
diff --git a/api/test-current.txt b/api/test-current.txt
index d57269e48e64..5082c57cd221 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -17,6 +17,7 @@ package android {
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";
@@ -1313,6 +1314,8 @@ package android.hardware.display {
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
}
@@ -1817,7 +1820,7 @@ package android.media {
}
public final class MediaTranscodeManager {
- method @NonNull public android.media.MediaTranscodeManager.TranscodingJob enqueueRequest(@NonNull android.media.MediaTranscodeManager.TranscodingRequest, @NonNull java.util.concurrent.Executor, @NonNull android.media.MediaTranscodeManager.OnTranscodingFinishedListener) throws java.io.FileNotFoundException;
+ method @NonNull public android.media.MediaTranscodeManager.TranscodingJob enqueueRequest(@NonNull android.media.MediaTranscodeManager.TranscodingRequest, @NonNull java.util.concurrent.Executor, @NonNull android.media.MediaTranscodeManager.OnTranscodingFinishedListener) throws java.io.FileNotFoundException, android.media.MediaTranscodingException.ServiceNotAvailableException;
field public static final int PRIORITY_REALTIME = 1; // 0x1
field public static final int TRANSCODING_TYPE_VIDEO = 1; // 0x1
}
@@ -1832,7 +1835,6 @@ package android.media {
method @IntRange(from=0, to=100) public int getProgress();
method public int getResult();
method public int getStatus();
- method public boolean retry();
method public void setOnProgressUpdateListener(@NonNull java.util.concurrent.Executor, @Nullable android.media.MediaTranscodeManager.TranscodingJob.OnProgressUpdateListener);
method public void setOnProgressUpdateListener(int, @NonNull java.util.concurrent.Executor, @Nullable android.media.MediaTranscodeManager.TranscodingJob.OnProgressUpdateListener);
field public static final int RESULT_CANCELED = 4; // 0x4
@@ -3292,6 +3294,7 @@ package android.provider {
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";
@@ -4139,7 +4142,8 @@ package android.telephony {
public class PhoneStateListener {
method @Deprecated public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber);
method public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber, int);
- method public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber);
+ 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
}
diff --git a/api/test-lint-baseline.txt b/api/test-lint-baseline.txt
index 63ba4aa16662..91a09e3d8890 100644
--- a/api/test-lint-baseline.txt
+++ b/api/test-lint-baseline.txt
@@ -74,11 +74,11 @@ ActionValue: android.telephony.mbms.vendor.VendorUtils#EXTRA_TEMP_LIST:
ArrayReturn: android.app.UiAutomation#executeShellCommandRw(String):
ArrayReturn: android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel#KeyphraseSoundModel(java.util.UUID, java.util.UUID, byte[], android.hardware.soundtrigger.SoundTrigger.Keyphrase[]) parameter #3:
- Method parameter should be Collection<Keyphrase> (or subclass) instead of raw array; was `android.hardware.soundtrigger.SoundTrigger.Keyphrase[]`
+
ArrayReturn: android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel#KeyphraseSoundModel(java.util.UUID, java.util.UUID, byte[], android.hardware.soundtrigger.SoundTrigger.Keyphrase[], int) parameter #3:
- Method parameter should be Collection<Keyphrase> (or subclass) instead of raw array; was `android.hardware.soundtrigger.SoundTrigger.Keyphrase[]`
+
ArrayReturn: android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel#getKeyphrases():
- Method should return Collection<Keyphrase> (or subclass) instead of raw array; was `android.hardware.soundtrigger.SoundTrigger.Keyphrase[]`
+
ArrayReturn: android.location.GnssMeasurementsEvent#GnssMeasurementsEvent(android.location.GnssClock, android.location.GnssMeasurement[]) parameter #1:
ArrayReturn: android.media.AudioRecordingConfiguration#AudioRecordingConfiguration(int, int, int, android.media.AudioFormat, android.media.AudioFormat, int, String, int, boolean, int, android.media.audiofx.AudioEffect.Descriptor[], android.media.audiofx.AudioEffect.Descriptor[]) parameter #10:
@@ -218,33 +218,33 @@ BannedThrow: android.os.Process#getThreadScheduler(int):
BuilderSetStyle: android.media.audiopolicy.AudioMixingRule.Builder#allowPrivilegedPlaybackCapture(boolean):
- Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.media.audiopolicy.AudioMixingRule.Builder.allowPrivilegedPlaybackCapture(boolean)
+
BuilderSetStyle: android.media.audiopolicy.AudioMixingRule.Builder#excludeMixRule(int, Object):
- Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.media.audiopolicy.AudioMixingRule.Builder.excludeMixRule(int,Object)
+
BuilderSetStyle: android.media.audiopolicy.AudioMixingRule.Builder#excludeRule(android.media.AudioAttributes, int):
- Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.media.audiopolicy.AudioMixingRule.Builder.excludeRule(android.media.AudioAttributes,int)
+
BuilderSetStyle: android.net.NetworkCapabilities.Builder#removeCapability(int):
- Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.net.NetworkCapabilities.Builder.removeCapability(int)
+
BuilderSetStyle: android.net.NetworkCapabilities.Builder#removeTransportType(int):
- Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.net.NetworkCapabilities.Builder.removeTransportType(int)
+
BuilderSetStyle: android.net.metrics.RaEvent.Builder#updateDnsslLifetime(long):
- Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.net.metrics.RaEvent.Builder.updateDnsslLifetime(long)
+
BuilderSetStyle: android.net.metrics.RaEvent.Builder#updatePrefixPreferredLifetime(long):
- Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.net.metrics.RaEvent.Builder.updatePrefixPreferredLifetime(long)
+
BuilderSetStyle: android.net.metrics.RaEvent.Builder#updatePrefixValidLifetime(long):
- Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.net.metrics.RaEvent.Builder.updatePrefixValidLifetime(long)
+
BuilderSetStyle: android.net.metrics.RaEvent.Builder#updateRdnssLifetime(long):
- Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.net.metrics.RaEvent.Builder.updateRdnssLifetime(long)
+
BuilderSetStyle: android.net.metrics.RaEvent.Builder#updateRouteInfoLifetime(long):
- Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.net.metrics.RaEvent.Builder.updateRouteInfoLifetime(long)
+
BuilderSetStyle: android.net.metrics.RaEvent.Builder#updateRouterLifetime(long):
- Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.net.metrics.RaEvent.Builder.updateRouterLifetime(long)
+
BuilderSetStyle: android.os.StrictMode.ThreadPolicy.Builder#detectExplicitGc():
- Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.os.StrictMode.ThreadPolicy.Builder.detectExplicitGc()
+
BuilderSetStyle: android.os.StrictMode.VmPolicy.Builder#detectIncorrectContextUse():
- Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.os.StrictMode.VmPolicy.Builder.detectIncorrectContextUse()
+
BuilderSetStyle: android.os.StrictMode.VmPolicy.Builder#permitIncorrectContextUse():
- Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.os.StrictMode.VmPolicy.Builder.permitIncorrectContextUse()
+
CallbackInterface: android.app.prediction.AppPredictor.Callback:
@@ -382,11 +382,11 @@ ExecutorRegistration: android.os.IncidentManager#requestAuthorization(int, Strin
ExecutorRegistration: android.os.RemoteCallback#RemoteCallback(android.os.RemoteCallback.OnResultListener, android.os.Handler):
ExecutorRegistration: android.permission.PermissionControllerManager#countPermissionApps(java.util.List<java.lang.String>, int, android.permission.PermissionControllerManager.OnCountPermissionAppsResultCallback, android.os.Handler):
- Registration methods should have overload that accepts delivery Executor: `countPermissionApps`
+
ExecutorRegistration: android.permission.PermissionControllerManager#getAppPermissions(String, android.permission.PermissionControllerManager.OnGetAppPermissionResultCallback, android.os.Handler):
ExecutorRegistration: android.service.watchdog.ExplicitHealthCheckService#setCallback(android.os.RemoteCallback):
- Registration methods should have overload that accepts delivery Executor: `setCallback`
+
ExecutorRegistration: android.telephony.ims.stub.ImsCallSessionImplBase#setListener(android.telephony.ims.ImsCallSessionListener):
ExecutorRegistration: android.telephony.ims.stub.ImsUtImplBase#setListener(android.telephony.ims.ImsUtListener):
@@ -406,7 +406,7 @@ ExecutorRegistration: android.telephony.mbms.vendor.MbmsStreamingServiceBase#ini
ExecutorRegistration: android.telephony.mbms.vendor.MbmsStreamingServiceBase#startStreaming(int, String, android.telephony.mbms.StreamingServiceCallback):
ExecutorRegistration: android.window.WindowOrganizer#applySyncTransaction(android.window.WindowContainerTransaction, android.window.WindowContainerTransactionCallback):
- Registration methods should have overload that accepts delivery Executor: `applySyncTransaction`
+
ForbiddenSuperClass: android.app.AppDetailsActivity:
@@ -428,9 +428,9 @@ GenericException: android.service.autofill.augmented.FillWindow#finalize():
GetterOnBuilder: android.hardware.display.BrightnessConfiguration.Builder#getMaxCorrectionsByCategory():
- Getter should be on the built object, not the builder: method android.hardware.display.BrightnessConfiguration.Builder.getMaxCorrectionsByCategory()
+
GetterOnBuilder: android.hardware.display.BrightnessConfiguration.Builder#getMaxCorrectionsByPackageName():
- Getter should be on the built object, not the builder: method android.hardware.display.BrightnessConfiguration.Builder.getMaxCorrectionsByPackageName()
+
GetterSetterNames: android.app.NotificationChannel#isBlockableSystem():
@@ -510,7 +510,7 @@ IntentBuilderName: android.app.backup.BackupManager#getConfigurationIntent(Strin
IntentBuilderName: android.app.backup.BackupManager#getDataManagementIntent(String):
IntentBuilderName: android.hardware.soundtrigger.KeyphraseEnrollmentInfo#getManageKeyphraseIntent(int, String, java.util.Locale):
- Methods creating an Intent should be named `create<Foo>Intent()`, was `getManageKeyphraseIntent`
+
IntentName: android.provider.Settings.Secure#VOICE_INTERACTION_SERVICE:
@@ -560,7 +560,7 @@ ListenerLast: android.hardware.camera2.CameraDevice#createCustomCaptureSession(a
ListenerLast: android.location.LocationManager#requestLocationUpdates(android.location.LocationRequest, android.location.LocationListener, android.os.Looper) parameter #2:
ListenerLast: android.permission.PermissionControllerManager#countPermissionApps(java.util.List<java.lang.String>, int, android.permission.PermissionControllerManager.OnCountPermissionAppsResultCallback, android.os.Handler) parameter #3:
- Listeners should always be at end of argument list (method `countPermissionApps`)
+
ListenerLast: android.permission.PermissionControllerManager#getAppPermissions(String, android.permission.PermissionControllerManager.OnGetAppPermissionResultCallback, android.os.Handler) parameter #2:
ListenerLast: android.telephony.mbms.vendor.MbmsGroupCallServiceBase#initialize(android.telephony.mbms.MbmsGroupCallSessionCallback, int) parameter #1:
@@ -593,140 +593,152 @@ MinMaxConstant: android.view.autofill.AutofillManager#MAX_TEMP_AUGMENTED_SERVICE
+MissingGetterMatchingBuilder: android.app.ActivityView.Builder#setAttributeSet(android.util.AttributeSet):
+ android.app.ActivityView does not declare a `getAttributeSet()` method matching method android.app.ActivityView.Builder.setAttributeSet(android.util.AttributeSet)
+MissingGetterMatchingBuilder: android.app.ActivityView.Builder#setDefaultStyle(int):
+ android.app.ActivityView does not declare a `getDefaultStyle()` method matching method android.app.ActivityView.Builder.setDefaultStyle(int)
+MissingGetterMatchingBuilder: android.app.ActivityView.Builder#setDisableSurfaceViewBackgroundLayer(boolean):
+ android.app.ActivityView does not declare a `isDisableSurfaceViewBackgroundLayer()` method matching method android.app.ActivityView.Builder.setDisableSurfaceViewBackgroundLayer(boolean)
+MissingGetterMatchingBuilder: android.app.ActivityView.Builder#setSingleInstance(boolean):
+ android.app.ActivityView does not declare a `isSingleInstance()` method matching method android.app.ActivityView.Builder.setSingleInstance(boolean)
+MissingGetterMatchingBuilder: android.app.ActivityView.Builder#setUsePublicVirtualDisplay(boolean):
+ android.app.ActivityView does not declare a `isUsePublicVirtualDisplay()` method matching method android.app.ActivityView.Builder.setUsePublicVirtualDisplay(boolean)
+MissingGetterMatchingBuilder: android.app.ActivityView.Builder#setUseTrustedDisplay(boolean):
+ android.app.ActivityView does not declare a `isUseTrustedDisplay()` method matching method android.app.ActivityView.Builder.setUseTrustedDisplay(boolean)
MissingGetterMatchingBuilder: android.app.AppOpsManager.HistoricalOpsRequest.Builder#setAttributionTag(String):
- android.app.AppOpsManager.HistoricalOpsRequest does not declare a `getAttributionTag()` method matching method android.app.AppOpsManager.HistoricalOpsRequest.Builder.setAttributionTag(String)
+
MissingGetterMatchingBuilder: android.app.AppOpsManager.HistoricalOpsRequest.Builder#setFlags(int):
- android.app.AppOpsManager.HistoricalOpsRequest does not declare a `getFlags()` method matching method android.app.AppOpsManager.HistoricalOpsRequest.Builder.setFlags(int)
+
MissingGetterMatchingBuilder: android.app.AppOpsManager.HistoricalOpsRequest.Builder#setOpNames(java.util.List<java.lang.String>):
- android.app.AppOpsManager.HistoricalOpsRequest does not declare a `getOpNames()` method matching method android.app.AppOpsManager.HistoricalOpsRequest.Builder.setOpNames(java.util.List<java.lang.String>)
+
MissingGetterMatchingBuilder: android.app.AppOpsManager.HistoricalOpsRequest.Builder#setPackageName(String):
- android.app.AppOpsManager.HistoricalOpsRequest does not declare a `getPackageName()` method matching method android.app.AppOpsManager.HistoricalOpsRequest.Builder.setPackageName(String)
+
MissingGetterMatchingBuilder: android.app.AppOpsManager.HistoricalOpsRequest.Builder#setUid(int):
- android.app.AppOpsManager.HistoricalOpsRequest does not declare a `getUid()` method matching method android.app.AppOpsManager.HistoricalOpsRequest.Builder.setUid(int)
+
MissingGetterMatchingBuilder: android.content.integrity.RuleSet.Builder#addRules(java.util.List<android.content.integrity.Rule>):
- android.content.integrity.RuleSet does not declare a `getRuless()` method matching method android.content.integrity.RuleSet.Builder.addRules(java.util.List<android.content.integrity.Rule>)
+
MissingGetterMatchingBuilder: android.hardware.display.BrightnessConfiguration.Builder#addCorrectionByCategory(int, android.hardware.display.BrightnessCorrection):
- android.hardware.display.BrightnessConfiguration does not declare a `getCorrectionByCategorys()` method matching method android.hardware.display.BrightnessConfiguration.Builder.addCorrectionByCategory(int,android.hardware.display.BrightnessCorrection)
+
MissingGetterMatchingBuilder: android.hardware.display.BrightnessConfiguration.Builder#addCorrectionByPackageName(String, android.hardware.display.BrightnessCorrection):
- android.hardware.display.BrightnessConfiguration does not declare a `getCorrectionByPackageNames()` method matching method android.hardware.display.BrightnessConfiguration.Builder.addCorrectionByPackageName(String,android.hardware.display.BrightnessCorrection)
+
MissingGetterMatchingBuilder: android.hardware.display.BrightnessConfiguration.Builder#setDescription(String):
- android.hardware.display.BrightnessConfiguration does not declare a `getDescription()` method matching method android.hardware.display.BrightnessConfiguration.Builder.setDescription(String)
+
MissingGetterMatchingBuilder: android.hardware.lights.LightsRequest.Builder#setLight(android.hardware.lights.Light, android.hardware.lights.LightState):
- android.hardware.lights.LightsRequest does not declare a `getLight()` method matching method android.hardware.lights.LightsRequest.Builder.setLight(android.hardware.lights.Light,android.hardware.lights.LightState)
+
MissingGetterMatchingBuilder: android.media.VolumeShaper.Configuration.Builder#setOptionFlags(int):
- android.media.VolumeShaper.Configuration does not declare a `getOptionFlags()` method matching method android.media.VolumeShaper.Configuration.Builder.setOptionFlags(int)
+
MissingGetterMatchingBuilder: android.media.audiopolicy.AudioMix.Builder#setDevice(android.media.AudioDeviceInfo):
- android.media.audiopolicy.AudioMix does not declare a `getDevice()` method matching method android.media.audiopolicy.AudioMix.Builder.setDevice(android.media.AudioDeviceInfo)
+
MissingGetterMatchingBuilder: android.media.audiopolicy.AudioMix.Builder#setFormat(android.media.AudioFormat):
- android.media.audiopolicy.AudioMix does not declare a `getFormat()` method matching method android.media.audiopolicy.AudioMix.Builder.setFormat(android.media.AudioFormat)
+
MissingGetterMatchingBuilder: android.media.audiopolicy.AudioMix.Builder#setRouteFlags(int):
- android.media.audiopolicy.AudioMix does not declare a `getRouteFlags()` method matching method android.media.audiopolicy.AudioMix.Builder.setRouteFlags(int)
+
MissingGetterMatchingBuilder: android.media.audiopolicy.AudioMixingRule.Builder#addMixRule(int, Object):
- android.media.audiopolicy.AudioMixingRule does not declare a `getMixRules()` method matching method android.media.audiopolicy.AudioMixingRule.Builder.addMixRule(int,Object)
+
MissingGetterMatchingBuilder: android.media.audiopolicy.AudioMixingRule.Builder#addRule(android.media.AudioAttributes, int):
- android.media.audiopolicy.AudioMixingRule does not declare a `getRules()` method matching method android.media.audiopolicy.AudioMixingRule.Builder.addRule(android.media.AudioAttributes,int)
+
MissingGetterMatchingBuilder: android.media.audiopolicy.AudioPolicy.Builder#addMix(android.media.audiopolicy.AudioMix):
- android.media.audiopolicy.AudioPolicy does not declare a `getMixs()` method matching method android.media.audiopolicy.AudioPolicy.Builder.addMix(android.media.audiopolicy.AudioMix)
+
MissingGetterMatchingBuilder: android.media.audiopolicy.AudioPolicy.Builder#setAudioPolicyFocusListener(android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener):
- android.media.audiopolicy.AudioPolicy does not declare a `getAudioPolicyFocusListener()` method matching method android.media.audiopolicy.AudioPolicy.Builder.setAudioPolicyFocusListener(android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener)
+
MissingGetterMatchingBuilder: android.media.audiopolicy.AudioPolicy.Builder#setAudioPolicyStatusListener(android.media.audiopolicy.AudioPolicy.AudioPolicyStatusListener):
- android.media.audiopolicy.AudioPolicy does not declare a `getAudioPolicyStatusListener()` method matching method android.media.audiopolicy.AudioPolicy.Builder.setAudioPolicyStatusListener(android.media.audiopolicy.AudioPolicy.AudioPolicyStatusListener)
+
MissingGetterMatchingBuilder: android.media.audiopolicy.AudioPolicy.Builder#setAudioPolicyVolumeCallback(android.media.audiopolicy.AudioPolicy.AudioPolicyVolumeCallback):
- android.media.audiopolicy.AudioPolicy does not declare a `getAudioPolicyVolumeCallback()` method matching method android.media.audiopolicy.AudioPolicy.Builder.setAudioPolicyVolumeCallback(android.media.audiopolicy.AudioPolicy.AudioPolicyVolumeCallback)
+
MissingGetterMatchingBuilder: android.media.audiopolicy.AudioPolicy.Builder#setIsAudioFocusPolicy(boolean):
- android.media.audiopolicy.AudioPolicy does not declare a `isIsAudioFocusPolicy()` method matching method android.media.audiopolicy.AudioPolicy.Builder.setIsAudioFocusPolicy(boolean)
+
MissingGetterMatchingBuilder: android.media.audiopolicy.AudioPolicy.Builder#setIsTestFocusPolicy(boolean):
- android.media.audiopolicy.AudioPolicy does not declare a `isIsTestFocusPolicy()` method matching method android.media.audiopolicy.AudioPolicy.Builder.setIsTestFocusPolicy(boolean)
+
MissingGetterMatchingBuilder: android.media.audiopolicy.AudioPolicy.Builder#setLooper(android.os.Looper):
- android.media.audiopolicy.AudioPolicy does not declare a `getLooper()` method matching method android.media.audiopolicy.AudioPolicy.Builder.setLooper(android.os.Looper)
+
MissingGetterMatchingBuilder: android.net.CaptivePortalData.Builder#setBytesRemaining(long):
- android.net.CaptivePortalData does not declare a `getBytesRemaining()` method matching method android.net.CaptivePortalData.Builder.setBytesRemaining(long)
+
MissingGetterMatchingBuilder: android.net.CaptivePortalData.Builder#setExpiryTime(long):
- android.net.CaptivePortalData does not declare a `getExpiryTime()` method matching method android.net.CaptivePortalData.Builder.setExpiryTime(long)
+
MissingGetterMatchingBuilder: android.net.CaptivePortalData.Builder#setRefreshTime(long):
- android.net.CaptivePortalData does not declare a `getRefreshTime()` method matching method android.net.CaptivePortalData.Builder.setRefreshTime(long)
+
MissingGetterMatchingBuilder: android.net.NetworkCapabilities.Builder#addCapability(int):
- android.net.NetworkCapabilities does not declare a `getCapabilitys()` method matching method android.net.NetworkCapabilities.Builder.addCapability(int)
+
MissingGetterMatchingBuilder: android.net.NetworkCapabilities.Builder#setRequestorPackageName(String):
- android.net.NetworkCapabilities does not declare a `getRequestorPackageName()` method matching method android.net.NetworkCapabilities.Builder.setRequestorPackageName(String)
+
MissingGetterMatchingBuilder: android.net.NetworkCapabilities.Builder#setRequestorUid(int):
- android.net.NetworkCapabilities does not declare a `getRequestorUid()` method matching method android.net.NetworkCapabilities.Builder.setRequestorUid(int)
+
MissingGetterMatchingBuilder: android.net.TetheringManager.TetheringRequest.Builder#setShouldShowEntitlementUi(boolean):
- android.net.TetheringManager.TetheringRequest does not declare a `shouldShowEntitlementUi()` method matching method android.net.TetheringManager.TetheringRequest.Builder.setShouldShowEntitlementUi(boolean)
+
MissingGetterMatchingBuilder: android.net.TetheringManager.TetheringRequest.Builder#setStaticIpv4Addresses(android.net.LinkAddress, android.net.LinkAddress):
- android.net.TetheringManager.TetheringRequest does not declare a `getStaticIpv4Addresses()` method matching method android.net.TetheringManager.TetheringRequest.Builder.setStaticIpv4Addresses(android.net.LinkAddress,android.net.LinkAddress)
+
MissingGetterMatchingBuilder: android.net.metrics.ApfProgramEvent.Builder#setActualLifetime(long):
- android.net.metrics.ApfProgramEvent does not declare a `getActualLifetime()` method matching method android.net.metrics.ApfProgramEvent.Builder.setActualLifetime(long)
+
MissingGetterMatchingBuilder: android.net.metrics.ApfProgramEvent.Builder#setCurrentRas(int):
- android.net.metrics.ApfProgramEvent does not declare a `getCurrentRas()` method matching method android.net.metrics.ApfProgramEvent.Builder.setCurrentRas(int)
+
MissingGetterMatchingBuilder: android.net.metrics.ApfProgramEvent.Builder#setFilteredRas(int):
- android.net.metrics.ApfProgramEvent does not declare a `getFilteredRas()` method matching method android.net.metrics.ApfProgramEvent.Builder.setFilteredRas(int)
+
MissingGetterMatchingBuilder: android.net.metrics.ApfProgramEvent.Builder#setFlags(boolean, boolean):
- android.net.metrics.ApfProgramEvent does not declare a `isFlags()` method matching method android.net.metrics.ApfProgramEvent.Builder.setFlags(boolean,boolean)
+
MissingGetterMatchingBuilder: android.net.metrics.ApfProgramEvent.Builder#setLifetime(long):
- android.net.metrics.ApfProgramEvent does not declare a `getLifetime()` method matching method android.net.metrics.ApfProgramEvent.Builder.setLifetime(long)
+
MissingGetterMatchingBuilder: android.net.metrics.ApfProgramEvent.Builder#setProgramLength(int):
- android.net.metrics.ApfProgramEvent does not declare a `getProgramLength()` method matching method android.net.metrics.ApfProgramEvent.Builder.setProgramLength(int)
+
MissingGetterMatchingBuilder: android.net.metrics.ApfStats.Builder#setDroppedRas(int):
- android.net.metrics.ApfStats does not declare a `getDroppedRas()` method matching method android.net.metrics.ApfStats.Builder.setDroppedRas(int)
+
MissingGetterMatchingBuilder: android.net.metrics.ApfStats.Builder#setDurationMs(long):
- android.net.metrics.ApfStats does not declare a `getDurationMs()` method matching method android.net.metrics.ApfStats.Builder.setDurationMs(long)
+
MissingGetterMatchingBuilder: android.net.metrics.ApfStats.Builder#setMatchingRas(int):
- android.net.metrics.ApfStats does not declare a `getMatchingRas()` method matching method android.net.metrics.ApfStats.Builder.setMatchingRas(int)
+
MissingGetterMatchingBuilder: android.net.metrics.ApfStats.Builder#setMaxProgramSize(int):
- android.net.metrics.ApfStats does not declare a `getMaxProgramSize()` method matching method android.net.metrics.ApfStats.Builder.setMaxProgramSize(int)
+
MissingGetterMatchingBuilder: android.net.metrics.ApfStats.Builder#setParseErrors(int):
- android.net.metrics.ApfStats does not declare a `getParseErrors()` method matching method android.net.metrics.ApfStats.Builder.setParseErrors(int)
+
MissingGetterMatchingBuilder: android.net.metrics.ApfStats.Builder#setProgramUpdates(int):
- android.net.metrics.ApfStats does not declare a `getProgramUpdates()` method matching method android.net.metrics.ApfStats.Builder.setProgramUpdates(int)
+
MissingGetterMatchingBuilder: android.net.metrics.ApfStats.Builder#setProgramUpdatesAll(int):
- android.net.metrics.ApfStats does not declare a `getProgramUpdatesAll()` method matching method android.net.metrics.ApfStats.Builder.setProgramUpdatesAll(int)
+
MissingGetterMatchingBuilder: android.net.metrics.ApfStats.Builder#setProgramUpdatesAllowingMulticast(int):
- android.net.metrics.ApfStats does not declare a `getProgramUpdatesAllowingMulticast()` method matching method android.net.metrics.ApfStats.Builder.setProgramUpdatesAllowingMulticast(int)
+
MissingGetterMatchingBuilder: android.net.metrics.ApfStats.Builder#setReceivedRas(int):
- android.net.metrics.ApfStats does not declare a `getReceivedRas()` method matching method android.net.metrics.ApfStats.Builder.setReceivedRas(int)
+
MissingGetterMatchingBuilder: android.net.metrics.ApfStats.Builder#setZeroLifetimeRas(int):
- android.net.metrics.ApfStats does not declare a `getZeroLifetimeRas()` method matching method android.net.metrics.ApfStats.Builder.setZeroLifetimeRas(int)
+
MissingGetterMatchingBuilder: android.net.metrics.DhcpClientEvent.Builder#setDurationMs(int):
- android.net.metrics.DhcpClientEvent does not declare a `getDurationMs()` method matching method android.net.metrics.DhcpClientEvent.Builder.setDurationMs(int)
+
MissingGetterMatchingBuilder: android.net.metrics.DhcpClientEvent.Builder#setMsg(String):
- android.net.metrics.DhcpClientEvent does not declare a `getMsg()` method matching method android.net.metrics.DhcpClientEvent.Builder.setMsg(String)
+
MissingGetterMatchingBuilder: android.net.metrics.ValidationProbeEvent.Builder#setDurationMs(long):
- android.net.metrics.ValidationProbeEvent does not declare a `getDurationMs()` method matching method android.net.metrics.ValidationProbeEvent.Builder.setDurationMs(long)
+
MissingGetterMatchingBuilder: android.net.metrics.ValidationProbeEvent.Builder#setProbeType(int, boolean):
- android.net.metrics.ValidationProbeEvent does not declare a `getProbeType()` method matching method android.net.metrics.ValidationProbeEvent.Builder.setProbeType(int,boolean)
+
MissingGetterMatchingBuilder: android.net.metrics.ValidationProbeEvent.Builder#setReturnCode(int):
- android.net.metrics.ValidationProbeEvent does not declare a `getReturnCode()` method matching method android.net.metrics.ValidationProbeEvent.Builder.setReturnCode(int)
+
MissingGetterMatchingBuilder: android.security.keystore.KeyGenParameterSpec.Builder#setUniqueIdIncluded(boolean):
- android.security.keystore.KeyGenParameterSpec does not declare a `isUniqueIdIncluded()` method matching method android.security.keystore.KeyGenParameterSpec.Builder.setUniqueIdIncluded(boolean)
+
MissingGetterMatchingBuilder: android.service.autofill.Dataset.Builder#setFieldInlinePresentation(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, java.util.regex.Pattern, android.service.autofill.InlinePresentation):
- android.service.autofill.Dataset does not declare a `getFieldInlinePresentation()` method matching method android.service.autofill.Dataset.Builder.setFieldInlinePresentation(android.view.autofill.AutofillId,android.view.autofill.AutofillValue,java.util.regex.Pattern,android.service.autofill.InlinePresentation)
+
MissingGetterMatchingBuilder: android.service.autofill.augmented.FillResponse.Builder#setClientState(android.os.Bundle):
- android.service.autofill.augmented.FillResponse does not declare a `getClientState()` method matching method android.service.autofill.augmented.FillResponse.Builder.setClientState(android.os.Bundle)
+
MissingGetterMatchingBuilder: android.service.autofill.augmented.FillResponse.Builder#setFillWindow(android.service.autofill.augmented.FillWindow):
- android.service.autofill.augmented.FillResponse does not declare a `getFillWindow()` method matching method android.service.autofill.augmented.FillResponse.Builder.setFillWindow(android.service.autofill.augmented.FillWindow)
+
MissingGetterMatchingBuilder: android.service.autofill.augmented.FillResponse.Builder#setInlineSuggestions(java.util.List<android.service.autofill.Dataset>):
- android.service.autofill.augmented.FillResponse does not declare a `getInlineSuggestions()` method matching method android.service.autofill.augmented.FillResponse.Builder.setInlineSuggestions(java.util.List<android.service.autofill.Dataset>)
+
MissingGetterMatchingBuilder: android.telecom.CallScreeningService.CallResponse.Builder#setShouldScreenCallViaAudioProcessing(boolean):
- android.telecom.CallScreeningService.CallResponse does not declare a `shouldScreenCallViaAudioProcessing()` method matching method android.telecom.CallScreeningService.CallResponse.Builder.setShouldScreenCallViaAudioProcessing(boolean)
+
MissingGetterMatchingBuilder: android.telecom.ConnectionRequest.Builder#setIsAdhocConferenceCall(boolean):
- android.telecom.ConnectionRequest does not declare a `isIsAdhocConferenceCall()` method matching method android.telecom.ConnectionRequest.Builder.setIsAdhocConferenceCall(boolean)
+
MissingGetterMatchingBuilder: android.telecom.ConnectionRequest.Builder#setRttPipeFromInCall(android.os.ParcelFileDescriptor):
- android.telecom.ConnectionRequest does not declare a `getRttPipeFromInCall()` method matching method android.telecom.ConnectionRequest.Builder.setRttPipeFromInCall(android.os.ParcelFileDescriptor)
+
MissingGetterMatchingBuilder: android.telecom.ConnectionRequest.Builder#setRttPipeToInCall(android.os.ParcelFileDescriptor):
- android.telecom.ConnectionRequest does not declare a `getRttPipeToInCall()` method matching method android.telecom.ConnectionRequest.Builder.setRttPipeToInCall(android.os.ParcelFileDescriptor)
+
MissingGetterMatchingBuilder: android.telecom.ConnectionRequest.Builder#setShouldShowIncomingCallUi(boolean):
- android.telecom.ConnectionRequest does not declare a `shouldShowIncomingCallUi()` method matching method android.telecom.ConnectionRequest.Builder.setShouldShowIncomingCallUi(boolean)
+
MissingGetterMatchingBuilder: android.telecom.PhoneAccount.Builder#setGroupId(String):
- android.telecom.PhoneAccount does not declare a `getGroupId()` method matching method android.telecom.PhoneAccount.Builder.setGroupId(String)
+
MissingGetterMatchingBuilder: android.telephony.NetworkRegistrationInfo.Builder#setEmergencyOnly(boolean):
- android.telephony.NetworkRegistrationInfo does not declare a `isEmergencyOnly()` method matching method android.telephony.NetworkRegistrationInfo.Builder.setEmergencyOnly(boolean)
+
MissingGetterMatchingBuilder: android.telephony.ims.ImsSsData.Builder#setCallForwardingInfo(java.util.List<android.telephony.ims.ImsCallForwardInfo>):
- android.telephony.ims.ImsSsData does not declare a `getCallForwardingInfo()` method matching method android.telephony.ims.ImsSsData.Builder.setCallForwardingInfo(java.util.List<android.telephony.ims.ImsCallForwardInfo>)
+
MissingGetterMatchingBuilder: android.telephony.ims.stub.ImsFeatureConfiguration.Builder#addFeature(int, int):
- android.telephony.ims.stub.ImsFeatureConfiguration does not declare a `getFeatures()` method matching method android.telephony.ims.stub.ImsFeatureConfiguration.Builder.addFeature(int,int)
+
MissingGetterMatchingBuilder: android.telephony.mbms.DownloadRequest.Builder#setServiceId(String):
- android.telephony.mbms.DownloadRequest does not declare a `getServiceId()` method matching method android.telephony.mbms.DownloadRequest.Builder.setServiceId(String)
+
MissingNullability: android.app.Activity#onMovedToDisplay(int, android.content.res.Configuration) parameter #1:
@@ -2590,7 +2602,7 @@ OnNameExpected: android.service.notification.NotificationAssistantService#attach
OnNameExpected: android.service.quicksettings.TileService#isQuickSettingsSupported():
OnNameExpected: android.service.watchdog.ExplicitHealthCheckService#setCallback(android.os.RemoteCallback):
- If implemented by developer, should follow the on<Something> style; otherwise consider marking final
+
OnNameExpected: android.telephony.ims.ImsService#createMmTelFeature(int):
OnNameExpected: android.telephony.ims.ImsService#createRcsFeature(int):
@@ -2620,11 +2632,11 @@ OnNameExpected: android.telephony.mbms.vendor.MbmsGroupCallServiceBase#updateGro
OptionalBuilderConstructorArgument: android.app.prediction.AppTargetEvent.Builder#Builder(android.app.prediction.AppTarget, int) parameter #0:
- Builder constructor arguments must be mandatory (i.e. not @Nullable): parameter target in android.app.prediction.AppTargetEvent.Builder(android.app.prediction.AppTarget target, int actionType)
+
OptionalBuilderConstructorArgument: android.net.CaptivePortalData.Builder#Builder(android.net.CaptivePortalData) parameter #0:
- Builder constructor arguments must be mandatory (i.e. not @Nullable): parameter data in android.net.CaptivePortalData.Builder(android.net.CaptivePortalData data)
+
OptionalBuilderConstructorArgument: android.os.VibrationAttributes.Builder#Builder(android.media.AudioAttributes, android.os.VibrationEffect) parameter #1:
- Builder constructor arguments must be mandatory (i.e. not @Nullable): parameter effect in android.os.VibrationAttributes.Builder(android.media.AudioAttributes audio, android.os.VibrationEffect effect)
+
PackageLayering: android.util.FeatureFlagUtils:
@@ -2824,7 +2836,7 @@ SamShouldBeLast: android.os.IHwBinder#linkToDeath(android.os.IHwBinder.DeathReci
SamShouldBeLast: android.os.StrictMode.ViolationInfo#dump(android.util.Printer, String):
SamShouldBeLast: android.permission.PermissionControllerManager#countPermissionApps(java.util.List<java.lang.String>, int, android.permission.PermissionControllerManager.OnCountPermissionAppsResultCallback, android.os.Handler):
- SAM-compatible parameters (such as parameter 3, "callback", in android.permission.PermissionControllerManager.countPermissionApps) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+
SamShouldBeLast: android.permission.PermissionControllerManager#getAppPermissions(String, android.permission.PermissionControllerManager.OnGetAppPermissionResultCallback, android.os.Handler):
SamShouldBeLast: android.permission.PermissionControllerManager#revokeRuntimePermissions(java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, java.util.concurrent.Executor, android.permission.PermissionControllerManager.OnRevokeRuntimePermissionsCallback):
@@ -2870,21 +2882,21 @@ SetterReturnsThis: android.media.audiopolicy.AudioPolicy.Builder#setAudioPolicyS
StaticFinalBuilder: android.content.integrity.RuleSet.Builder:
- Builder must be final: android.content.integrity.RuleSet.Builder
+
StaticFinalBuilder: android.hardware.display.BrightnessConfiguration.Builder:
- Builder must be final: android.hardware.display.BrightnessConfiguration.Builder
+
StaticFinalBuilder: android.media.audiopolicy.AudioMix.Builder:
- Builder must be final: android.media.audiopolicy.AudioMix.Builder
+
StaticFinalBuilder: android.media.audiopolicy.AudioMixingRule.Builder:
- Builder must be final: android.media.audiopolicy.AudioMixingRule.Builder
+
StaticFinalBuilder: android.media.audiopolicy.AudioPolicy.Builder:
- Builder must be final: android.media.audiopolicy.AudioPolicy.Builder
+
StaticFinalBuilder: android.net.CaptivePortalData.Builder:
- Builder must be final: android.net.CaptivePortalData.Builder
+
StaticFinalBuilder: android.net.TetheringManager.TetheringRequest.Builder:
- Builder must be final: android.net.TetheringManager.TetheringRequest.Builder
+
StaticFinalBuilder: android.telephony.ims.stub.ImsFeatureConfiguration.Builder:
- Builder must be final: android.telephony.ims.stub.ImsFeatureConfiguration.Builder
+
StaticUtils: android.os.health.HealthKeys:
@@ -2912,15 +2924,15 @@ StreamFiles: android.provider.MediaStore#scanVolume(android.content.Context, jav
UseIcu: android.hardware.soundtrigger.KeyphraseEnrollmentInfo#getKeyphraseMetadata(String, java.util.Locale) parameter #1:
- Type `java.util.Locale` should be replaced with richer ICU type `android.icu.util.ULocale`
+
UseIcu: android.hardware.soundtrigger.KeyphraseEnrollmentInfo#getManageKeyphraseIntent(int, String, java.util.Locale) parameter #2:
- Type `java.util.Locale` should be replaced with richer ICU type `android.icu.util.ULocale`
+
UseIcu: android.hardware.soundtrigger.KeyphraseMetadata#supportsLocale(java.util.Locale) parameter #0:
- Type `java.util.Locale` should be replaced with richer ICU type `android.icu.util.ULocale`
+
UseIcu: android.hardware.soundtrigger.SoundTrigger.Keyphrase#Keyphrase(int, int, java.util.Locale, String, int[]) parameter #2:
- Type `java.util.Locale` should be replaced with richer ICU type `android.icu.util.ULocale`
+
UseIcu: android.hardware.soundtrigger.SoundTrigger.Keyphrase#getLocale():
- Type `java.util.Locale` should be replaced with richer ICU type `android.icu.util.ULocale`
+
UseParcelFileDescriptor: android.util.proto.ProtoOutputStream#ProtoOutputStream(java.io.FileDescriptor) parameter #0:
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index fc1455be29b1..0a09801708a5 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -38,6 +38,7 @@ 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";
@@ -490,6 +491,8 @@ message Atom {
UIActionLatencyReported ui_action_latency_reported = 306 [(module) = "framework"];
WifiDisconnectReported wifi_disconnect_reported = 307 [(module) = "wifi"];
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"];
// StatsdStats tracks platform atoms with ids upto 500.
// Update StatsdStats::kMaxPushedAtomId when atom ids here approach that value.
@@ -11433,4 +11436,65 @@ message BlobInfo {
// List of leasees of this Blob
optional BlobLeaseeListProto leasees = 5 [(log_mode) = MODE_BYTES];
-} \ No newline at end of file
+}
+
+/**
+ * Logs when the HDMI CEC active source changes.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
+ */
+message HdmiCecActiveSourceChanged {
+ // The logical address of the active source.
+ optional android.stats.hdmi.LogicalAddress active_source_logical_address = 1;
+
+ // The physical address of the active source. Consists of four hexadecimal nibbles.
+ // Examples: 0x1234, 0x0000 (root device). 0xFFFF represents an unknown or invalid address.
+ // See section 8.7 in the HDMI 1.4b spec for details.
+ optional int32 active_source_physical_address = 2;
+
+ // The relationship between this device and the active source.
+ optional android.stats.hdmi.PathRelationship local_relationship = 3;
+}
+
+/**
+ * Logs when an HDMI CEC message is sent or received.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
+ */
+message HdmiCecMessageReported {
+ // The calling uid of the application that caused this atom to be written.
+ optional int32 uid = 1 [(is_uid) = true];
+
+ // Whether a HDMI CEC message is sent from this device, to this device, or neither.
+ optional android.stats.hdmi.MessageDirection direction = 2;
+
+ // The HDMI CEC logical address of the initiator.
+ optional android.stats.hdmi.LogicalAddress initiator_logical_address = 3;
+
+ // The HDMI CEC logical address of the destination.
+ optional android.stats.hdmi.LogicalAddress destination_logical_address = 4;
+
+ // The opcode of the message. Ranges from 0x00 to 0xFF.
+ // For all values, see section "CEC 15 Message Descriptions" in the HDMI CEC 1.4b spec.
+ optional int32 opcode = 5;
+
+ // The result of attempting to send the message on its final retransmission attempt.
+ // Only applicable to outgoing messages; set to SEND_MESSAGE_RESULT_UNKNOWN otherwise.
+ optional android.stats.hdmi.SendMessageResult send_message_result = 6;
+
+ // Fields specific to <User Control Pressed> messages
+
+ // The user control command that was received.
+ optional android.stats.hdmi.UserControlPressedCommand user_control_pressed_command = 7;
+
+ // Fields specific to <Feature Abort> messages
+
+ // The opcode of the message that was feature aborted.
+ // Set to 0x100 when unknown or not applicable.
+ optional int32 feature_abort_opcode = 8;
+
+ // The reason for the feature abort.
+ optional android.stats.hdmi.FeatureAbortReason feature_abort_reason = 9;
+}
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/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/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..320b28581a38 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,9 @@ protected:
FRIEND_TEST(ValueMetricE2eTest, TestInitialConditionChanges);
FRIEND_TEST(MetricsManagerTest, TestInitialConditions);
+
+ FRIEND_TEST(ConfigUpdateTest, TestUpdateMetricActivations);
+ FRIEND_TEST(ConfigUpdateTest, TestUpdateEventMetrics);
};
} // 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/parsing_utils/config_update_utils.cpp b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
index bd60b6bfcb8e..5dbf16deb552 100644
--- a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
+++ b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
@@ -21,6 +21,7 @@
#include "external/StatsPullerManager.h"
#include "hash.h"
+#include "matchers/EventMatcherWizard.h"
#include "metrics_manager_util.h"
namespace android {
@@ -394,6 +395,210 @@ 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 determineEventMetricUpdateStatus(const StatsdConfig& config, const EventMetric& metric,
+ 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,
+ UpdateStatus& updateStatus) {
+ int64_t id = metric.id();
+ // Check if new metric
+ const auto& oldMetricProducerIt = oldMetricProducerMap.find(id);
+ 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, id, metricToActivationMap, metricHash)) {
+ return false;
+ }
+ const sp<MetricProducer> oldMetricProducer = oldMetricProducers[oldMetricProducerIt->second];
+ if (oldMetricProducer->getMetricType() != METRIC_TYPE_EVENT ||
+ oldMetricProducer->getProtoHash() != metricHash) {
+ updateStatus = UPDATE_REPLACE;
+ return true;
+ }
+
+ // Metric type and definition are the same. Need to check dependencies to see if they changed.
+ if (replacedMatchers.find(metric.what()) != replacedMatchers.end()) {
+ updateStatus = UPDATE_REPLACE;
+ return true;
+ }
+
+ if (metric.has_condition()) {
+ if (replacedConditions.find(metric.condition()) != replacedConditions.end()) {
+ updateStatus = UPDATE_REPLACE;
+ return true;
+ }
+ }
+
+ if (metricActivationDepsChange(config, metricToActivationMap, id, replacedMatchers)) {
+ updateStatus = UPDATE_REPLACE;
+ return true;
+ }
+
+ for (const auto& metricConditionLink : metric.links()) {
+ if (replacedConditions.find(metricConditionLink.condition()) != replacedConditions.end()) {
+ updateStatus = UPDATE_REPLACE;
+ return true;
+ }
+ }
+ updateStatus = UPDATE_PRESERVE;
+ return true;
+}
+
+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);
+ int metricIndex = 0;
+ for (int i = 0; i < config.event_metric_size(); i++, metricIndex++) {
+ newMetricProducerMap[config.event_metric(i).id()] = metricIndex;
+ if (!determineEventMetricUpdateStatus(config, config.event_metric(i), oldMetricProducerMap,
+ oldMetricProducers, metricToActivationMap,
+ replacedMatchers, replacedConditions,
+ metricsToUpdate[metricIndex])) {
+ return false;
+ }
+ }
+
+ // TODO: determine update status for count, gauge, value, duration metrics.
+
+ // Now, perform the update. Must iterate the metric types in the same order
+ metricIndex = 0;
+ for (int i = 0; i < config.event_metric_size(); i++, metricIndex++) {
+ const EventMetric& metric = config.event_metric(i);
+ switch (metricsToUpdate[metricIndex]) {
+ case UPDATE_PRESERVE: {
+ const auto& oldMetricProducerIt = oldMetricProducerMap.find(metric.id());
+ if (oldMetricProducerIt == oldMetricProducerMap.end()) {
+ ALOGE("Could not find Metric %lld in the previous config, but expected it "
+ "to be there",
+ (long long)metric.id());
+ return false;
+ }
+ const int oldIndex = oldMetricProducerIt->second;
+ sp<MetricProducer> producer = oldMetricProducers[oldIndex];
+ producer->onConfigUpdated(
+ config, i, metricIndex, allAtomMatchingTrackers, oldAtomMatchingTrackerMap,
+ newAtomMatchingTrackerMap, matcherWizard, allConditionTrackers,
+ conditionTrackerMap, wizard, metricToActivationMap, trackerToMetricMap,
+ conditionToMetricMap, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation);
+ newMetricProducers.push_back(producer);
+ break;
+ }
+ case UPDATE_REPLACE:
+ case UPDATE_NEW: {
+ sp<MetricProducer> producer = createEventMetricProducerAndUpdateMetadata(
+ key, config, timeBaseNs, metric, metricIndex, allAtomMatchingTrackers,
+ newAtomMatchingTrackerMap, allConditionTrackers, conditionTrackerMap,
+ initialConditionCache, wizard, metricToActivationMap, trackerToMetricMap,
+ conditionToMetricMap, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation);
+ if (producer == nullptr) {
+ return false;
+ }
+ newMetricProducers.push_back(producer);
+ break;
+ }
+ default: {
+ ALOGE("Metric \"%lld\" update state is unknown. This should never happen",
+ (long long)metric.id());
+ return false;
+ }
+ }
+ }
+ // TODO: perform update for count, gauge, 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 +608,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 +648,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..1cd0ce524b31 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,68 @@ bool updateConditions(const ConfigKey& key, const StatsdConfig& config,
std::vector<ConditionState>& conditionCache,
std::set<int64_t>& replacedConditions);
+// Function to determine if an event metric needs to be updated. Populates updateStatus.
+// [config]: the input StatsdConfig
+// [metric]: the current metric to be updated
+// [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. conditions using these matchers must be replaced
+// output:
+// [updateStatus]: update status for the metric. Will be changed from UPDATE_UNKNOWN after this call
+// Returns whether the function was successful or not.
+bool determineEventMetricUpdateStatus(const StatsdConfig& config, const EventMetric& metric,
+ 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,
+ UpdateStatus& updateStatus);
+
+// 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 +198,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..51df7df5174e 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,130 @@ 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;
+}
+
+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 nullptr;
+ }
+ int trackerIndex;
+ if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex, false,
+ allAtomMatchingTrackers, atomMatchingTrackerMap,
+ trackerToMetricMap, trackerIndex)) {
+ return nullptr;
+ }
+
+ int conditionIndex = -1;
+ if (metric.has_condition()) {
+ if (!handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
+ metric.links(), allConditionTrackers, conditionIndex,
+ conditionToMetricMap)) {
+ return nullptr;
+ }
+ } else {
+ if (metric.links_size() > 0) {
+ ALOGW("metrics has a MetricConditionLink but doesn't have a condition");
+ return nullptr;
+ }
+ }
+
+ 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 nullptr;
+ }
+
+ return new EventMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard,
+ metricHash, timeBaseNs, eventActivationMap,
+ eventDeactivationMap);
+}
+
bool initAtomMatchingTrackers(const StatsdConfig& config, const sp<UidMap>& uidMap,
unordered_map<int64_t, int>& atomMatchingTrackerMap,
vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
@@ -492,18 +594,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;
}
@@ -608,18 +708,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 +735,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)) {
+ sp<MetricProducer> producer = createEventMetricProducerAndUpdateMetadata(
+ key, config, timeBaseTimeNs, metric, metricIndex, allAtomMatchingTrackers,
+ atomMatchingTrackerMap, allConditionTrackers, conditionTrackerMap,
+ initialConditionCache, wizard, metricToActivationMap, trackerToMetricMap,
+ conditionToMetricMap, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation);
+ if (producer == nullptr) {
return false;
}
-
- sp<MetricProducer> eventMetric = new EventMetricProducer(
- key, metric, conditionIndex, initialConditionCache, wizard, metricHash,
- timeBaseTimeNs, eventActivationMap, eventDeactivationMap);
-
- allMetricProducers.push_back(eventMetric);
+ allMetricProducers.push_back(producer);
}
// build ValueMetricProducer
@@ -759,18 +821,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;
}
@@ -832,9 +892,10 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t
if (metric.sampling_type() != GaugeMetric::FIRST_N_SAMPLES) {
return false;
}
- if (!handlePullMetricTriggerWithAtomMatchingTrackers(
- metric.trigger_event(), metricIndex, allAtomMatchingTrackers,
- atomMatchingTrackerMap, trackerToMetricMap, triggerTrackerIndex)) {
+ if (!handleMetricWithAtomMatchingTrackers(
+ metric.trigger_event(), metricIndex, /*enforceOneAtom=*/true,
+ allAtomMatchingTrackers, atomMatchingTrackerMap, trackerToMetricMap,
+ triggerTrackerIndex)) {
return false;
}
sp<AtomMatchingTracker> triggerAtomMatcher =
@@ -863,18 +924,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;
}
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..fdfe5cfa6491 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,61 @@ 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);
+
+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);
+
// Helper functions for MetricsManager to initialize from StatsdConfig.
// *Note*: only initStatsdConfig() should be called from outside.
// All other functions are intermediate
@@ -154,10 +209,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/metrics/parsing_utils/config_update_utils_test.cpp b/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
index 843d836a2c0b..076000fab80f 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
@@ -34,6 +34,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 +63,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,11 +88,11 @@ 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();
@@ -103,12 +105,22 @@ 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;
+}
+
} // anonymous namespace
TEST_F(ConfigUpdateTest, TestSimpleMatcherPreserve) {
@@ -660,7 +672,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 +745,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 +758,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 +899,562 @@ 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));
+
+ set<int64_t> replacedMatchers;
+ set<int64_t> replacedConditions;
+ unordered_map<int64_t, int> metricToActivationMap;
+ UpdateStatus status = UPDATE_UNKNOWN;
+ EXPECT_TRUE(determineEventMetricUpdateStatus(config, *metric, oldMetricProducerMap,
+ oldMetricProducers, metricToActivationMap,
+ replacedMatchers, replacedConditions, status));
+ EXPECT_EQ(status, 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);
+
+ set<int64_t> replacedMatchers;
+ set<int64_t> replacedConditions;
+ unordered_map<int64_t, int> metricToActivationMap = {{12345, 0}};
+ UpdateStatus status = UPDATE_UNKNOWN;
+ EXPECT_TRUE(determineEventMetricUpdateStatus(config, *metric, oldMetricProducerMap,
+ oldMetricProducers, metricToActivationMap,
+ replacedMatchers, replacedConditions, status));
+ EXPECT_EQ(status, 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));
+
+ set<int64_t> replacedMatchers = {whatMatcher.id()};
+ set<int64_t> replacedConditions;
+ unordered_map<int64_t, int> metricToActivationMap;
+ UpdateStatus status = UPDATE_UNKNOWN;
+ EXPECT_TRUE(determineEventMetricUpdateStatus(config, *metric, oldMetricProducerMap,
+ oldMetricProducers, metricToActivationMap,
+ replacedMatchers, replacedConditions, status));
+ EXPECT_EQ(status, 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));
+
+ set<int64_t> replacedMatchers;
+ set<int64_t> replacedConditions = {predicate.id()};
+ unordered_map<int64_t, int> metricToActivationMap;
+ UpdateStatus status = UPDATE_UNKNOWN;
+ EXPECT_TRUE(determineEventMetricUpdateStatus(config, *metric, oldMetricProducerMap,
+ oldMetricProducers, metricToActivationMap,
+ replacedMatchers, replacedConditions, status));
+ EXPECT_EQ(status, 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));
+
+ set<int64_t> replacedMatchers;
+ set<int64_t> replacedConditions = {linkPredicate.id()};
+ unordered_map<int64_t, int> metricToActivationMap;
+ UpdateStatus status = UPDATE_UNKNOWN;
+ EXPECT_TRUE(determineEventMetricUpdateStatus(config, *metric, oldMetricProducerMap,
+ oldMetricProducers, metricToActivationMap,
+ replacedMatchers, replacedConditions, status));
+ EXPECT_EQ(status, 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));
+
+ set<int64_t> replacedMatchers = {startMatcher.id()}; // The activation matcher is replaced.
+ set<int64_t> replacedConditions;
+ unordered_map<int64_t, int> metricToActivationMap = {{12345, 0}};
+ UpdateStatus status = UPDATE_UNKNOWN;
+ EXPECT_TRUE(determineEventMetricUpdateStatus(config, *metric, oldMetricProducerMap,
+ oldMetricProducers, metricToActivationMap,
+ replacedMatchers, replacedConditions, status));
+ EXPECT_EQ(status, 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 conditions are different and included in replacedConditions.
+ 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, 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));
+}
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 1f8cf8ac6d1d..a2d0b892aa0a 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -46,20 +46,39 @@ 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}
- * if in the same profile group.
+ * or {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} 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 = 1;
+ 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_FULL_ONLY = 2;
/**
* Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES}
- * or {@link android.Manifest.permission#INTERACT_ACROSS_USERS} if in the same profile group.
+ * or {@link android.Manifest.permission#INTERACT_ACROSS_USERS} or
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} if in the same profile group.
* Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required.
*/
- public static final int ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE = 3;
+ 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;
/**
* Verify that calling app has access to the given provider.
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 0fd0e02a224a..312d122d0ba4 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -614,7 +614,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);
};
}
@@ -3475,13 +3475,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.");
}
@@ -3687,12 +3683,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);
}
@@ -3881,13 +3872,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
@@ -4417,22 +4402,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) {
@@ -4446,7 +4430,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;
@@ -4477,7 +4461,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) {
@@ -4498,20 +4482,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
@@ -4629,13 +4612,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);
}
@@ -4668,23 +4646,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) {
@@ -4781,8 +4756,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 */,
@@ -4823,44 +4801,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);
}
/**
@@ -4927,9 +4903,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();
@@ -4965,8 +4940,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) {
@@ -5053,110 +5027,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) {
@@ -5170,70 +5132,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();
}
@@ -5455,7 +5412,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;
@@ -5483,12 +5440,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) {
@@ -5647,13 +5602,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
@@ -5952,20 +5901,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)) {
@@ -5985,21 +5922,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) {
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 167b5a8029c0..04f72f6dc71d 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -6741,10 +6741,14 @@ 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);
}
}
@@ -6762,11 +6766,7 @@ public class AppOpsManager {
@TestApi
@RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
public void setUidMode(@NonNull String appOp, int uid, @Mode int mode) {
- try {
- mService.setUidMode(AppOpsManager.strOpToOp(appOp), uid, mode);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ setUidMode(AppOpsManager.strOpToOp(appOp), uid, mode);
}
/** @hide */
@@ -6795,10 +6795,14 @@ 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);
}
}
@@ -6818,11 +6822,7 @@ 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) {
- try {
- mService.setMode(strOpToOp(op), uid, packageName, mode);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ setMode(strOpToOp(op), uid, packageName, mode);
}
/**
@@ -7298,10 +7298,14 @@ 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);
}
}
@@ -7473,8 +7477,20 @@ public class AppOpsManager {
}
}
- int mode = mService.noteOperation(op, uid, packageName, attributionTag,
- collectionMode == COLLECT_ASYNC, message, shouldCollectMessage);
+ 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);
+ }
if (mode == MODE_ALLOWED) {
if (collectionMode == COLLECT_SELF) {
@@ -7637,10 +7653,17 @@ public class AppOpsManager {
}
}
- int mode = mService.noteProxyOperation(op, proxiedUid, proxiedPackageName,
- proxiedAttributionTag, myUid, mContext.getOpPackageName(),
- mContext.getAttributionTag(), collectionMode == COLLECT_ASYNC, message,
- shouldCollectMessage);
+ 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);
+ }
if (mode == MODE_ALLOWED) {
if (collectionMode == COLLECT_SELF) {
@@ -7690,6 +7713,8 @@ 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) {
@@ -7698,6 +7723,8 @@ public class AppOpsManager {
return mode;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
}
@@ -7708,11 +7735,15 @@ public class AppOpsManager {
*/
@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);
}
}
@@ -7964,9 +7995,16 @@ public class AppOpsManager {
}
}
- int mode = mService.startOperation(getClientId(), op, uid, packageName,
- attributionTag, startIfModeDefault, collectionMode == COLLECT_ASYNC, message,
- shouldCollectMessage);
+ 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);
+ }
if (mode == MODE_ALLOWED) {
if (collectionMode == COLLECT_SELF) {
@@ -8029,10 +8067,14 @@ public class AppOpsManager {
*/
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);
}
}
@@ -8624,10 +8666,14 @@ 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 066a00732965..3d966c7fbc38 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -471,6 +471,19 @@ public class ApplicationPackageManager extends PackageManager {
}
@Override
+ public int getTargetSdkVersion(@NonNull String packageName) throws NameNotFoundException {
+ try {
+ int version = mPM.getTargetSdkVersion(packageName);
+ if (version != -1) {
+ return version;
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ throw new PackageManager.NameNotFoundException(packageName);
+ }
+
+ @Override
public ActivityInfo getActivityInfo(ComponentName className, int flags)
throws NameNotFoundException {
final int userId = getUserId();
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..cf5a7b629e38 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2413,7 +2413,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 +2441,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 +2810,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/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 45b25a3bf317..c1e6f5282b2a 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -513,13 +513,6 @@ 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
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index f3c7fe9412c9..e21ef8ee23ad 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -295,12 +295,6 @@ 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);
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/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 1329fa45bb50..4ff9e8d58c3a 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -400,6 +400,7 @@ public class StatusBarManager {
* @hide
*/
@TestApi
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@RequiresPermission(android.Manifest.permission.STATUS_BAR)
public void setDisabledForSimNetworkLock(boolean disabled) {
try {
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 4139b2faeb1b..b0307d1eb939 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -70,7 +70,7 @@
"name": "CtsAutoFillServiceTestCases",
"options": [
{
- "include-filter": "android.autofillservice.cts.PreSimpleSaveActivityTest"
+ "include-filter": "android.autofillservice.cts.saveui.PreSimpleSaveActivityTest"
},
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
@@ -82,7 +82,7 @@
"name": "CtsAutoFillServiceTestCases",
"options": [
{
- "include-filter": "android.autofillservice.cts.SimpleSaveActivityTest"
+ "include-filter": "android.autofillservice.cts.saveui.SimpleSaveActivityTest"
},
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
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/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java
index 73c38cbfbe3f..ce3c7d25a108 100644
--- a/core/java/android/bluetooth/BluetoothPan.java
+++ b/core/java/android/bluetooth/BluetoothPan.java
@@ -89,6 +89,33 @@ public final class BluetoothPan implements BluetoothProfile {
@SuppressLint("ActionValue")
public static final String EXTRA_LOCAL_ROLE = "android.bluetooth.pan.extra.LOCAL_ROLE";
+ /**
+ * Intent used to broadcast the change in tethering state of the Pan
+ * Profile
+ *
+ * <p>This intent will have 1 extra:
+ * <ul>
+ * <li> {@link #EXTRA_TETHERING_STATE} - The current state of Bluetooth
+ * tethering. </li>
+ * </ul>
+ *
+ * <p> {@link #EXTRA_TETHERING_STATE} can be any of {@link #TETHERING_STATE_OFF} or
+ * {@link #TETHERING_STATE_ON}
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+ * receive.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_TETHERING_STATE_CHANGED =
+ "android.bluetooth.action.TETHERING_STATE_CHANGED";
+
+ /**
+ * Extra for {@link #ACTION_TETHERING_STATE_CHANGED} intent
+ * The tethering state of the PAN profile.
+ * It can be one of {@link #TETHERING_STATE_OFF} or {@link #TETHERING_STATE_ON}.
+ */
+ public static final String EXTRA_TETHERING_STATE =
+ "android.bluetooth.extra.TETHERING_STATE";
+
/** @hide */
@IntDef({PAN_ROLE_NONE, LOCAL_NAP_ROLE, LOCAL_PANU_ROLE})
@Retention(RetentionPolicy.SOURCE)
@@ -114,6 +141,14 @@ public final class BluetoothPan implements BluetoothProfile {
public static final int REMOTE_PANU_ROLE = 2;
+ /** @hide **/
+ @IntDef({TETHERING_STATE_OFF, TETHERING_STATE_ON})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TetheringState{}
+
+ public static final int TETHERING_STATE_OFF = 1;
+
+ public static final int TETHERING_STATE_ON = 2;
/**
* Return codes for the connect and disconnect Bluez / Dbus calls.
*
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 38bc79750631..5452f92c929f 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5820,10 +5820,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/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..c2beab507da0 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
diff --git a/core/java/android/content/pm/ApkChecksum.java b/core/java/android/content/pm/ApkChecksum.java
index e087c44d1ed1..bf678419cbef 100644
--- a/core/java/android/content/pm/ApkChecksum.java
+++ b/core/java/android/content/pm/ApkChecksum.java
@@ -50,7 +50,11 @@ 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 String mSourcePackageName;
+ /**
+ * For Installer-provided checksums, certificate of the Installer.
*/
private final @Nullable byte[] mSourceCertificate;
@@ -61,7 +65,7 @@ public final class ApkChecksum implements Parcelable {
*/
public ApkChecksum(@Nullable String splitName, @Checksum.Kind int kind,
@NonNull byte[] value) {
- this(splitName, new Checksum(kind, value), (byte[]) null);
+ this(splitName, new Checksum(kind, value), (String) null, (byte[]) null);
}
/**
@@ -69,10 +73,10 @@ 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.Kind int kind, @NonNull byte[] value,
+ @Nullable String sourcePackageName, @Nullable Certificate sourceCertificate)
throws CertificateEncodingException {
- this(splitName, new Checksum(kind, value),
+ this(splitName, new Checksum(kind, value), sourcePackageName,
(sourceCertificate != null) ? sourceCertificate.getEncoded() : null);
}
@@ -136,19 +140,23 @@ public final class ApkChecksum implements Parcelable {
* Checksum for which split. Null indicates base.apk.
* @param checksum
* Checksum.
+ * @param sourcePackageName
+ * For Installer-provided checksums, package name of the Installer.
* @param sourceCertificate
- * For Installer-provided checksums, certificate of the Installer/AppStore.
+ * For Installer-provided checksums, certificate of the Installer.
* @hide
*/
@DataClass.Generated.Member
public ApkChecksum(
@Nullable String splitName,
@NonNull Checksum checksum,
+ @Nullable String sourcePackageName,
@Nullable byte[] sourceCertificate) {
this.mSplitName = splitName;
this.mChecksum = checksum;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mChecksum);
+ this.mSourcePackageName = sourcePackageName;
this.mSourceCertificate = sourceCertificate;
// onConstructed(); // You can define this method to get a callback
@@ -162,6 +170,14 @@ public final class ApkChecksum implements Parcelable {
return mSplitName;
}
+ /**
+ * For Installer-provided checksums, package name of the Installer.
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getSourcePackageName() {
+ return mSourcePackageName;
+ }
+
@Override
@DataClass.Generated.Member
public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -170,10 +186,12 @@ public final class ApkChecksum implements Parcelable {
byte flg = 0;
if (mSplitName != null) flg |= 0x1;
- if (mSourceCertificate != null) flg |= 0x4;
+ if (mSourcePackageName != null) flg |= 0x4;
+ if (mSourceCertificate != null) flg |= 0x8;
dest.writeByte(flg);
if (mSplitName != null) dest.writeString(mSplitName);
dest.writeTypedObject(mChecksum, flags);
+ if (mSourcePackageName != null) dest.writeString(mSourcePackageName);
if (mSourceCertificate != null) dest.writeByteArray(mSourceCertificate);
}
@@ -191,12 +209,14 @@ 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 sourcePackageName = (flg & 0x4) == 0 ? null : in.readString();
+ byte[] sourceCertificate = (flg & 0x8) == 0 ? null : in.createByteArray();
this.mSplitName = splitName;
this.mChecksum = checksum;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mChecksum);
+ this.mSourcePackageName = sourcePackageName;
this.mSourceCertificate = sourceCertificate;
// onConstructed(); // You can define this method to get a callback
@@ -217,10 +237,10 @@ public final class ApkChecksum implements Parcelable {
};
@DataClass.Generated(
- time = 1599845645160L,
+ time = 1600407436287L,
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 mSourcePackageName\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)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 6673cb933eff..d5f2c12e8462 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -146,8 +146,15 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
public int uiOptions = 0;
/**
- * Value for {@link #flags}: if set, this application is installed in the
- * device's system image.
+ * Value for {@link #flags}: if set, this application is installed in the device's system image.
+ * This should not be used to make security decisions. Instead, rely on
+ * {@linkplain android.content.pm.PackageManager#checkSignatures(java.lang.String,java.lang.String)
+ * signature checks} or
+ * <a href="https://developer.android.com/training/articles/security-tips#Permissions">permissions</a>.
+ *
+ * <p><b>Warning:</b> Note that does flag not behave the same as
+ * {@link android.R.attr#protectionLevel android:protectionLevel} {@code system} or
+ * {@code signatureOrSystem}.
*/
public static final int FLAG_SYSTEM = 1<<0;
diff --git a/core/java/android/content/pm/Checksum.java b/core/java/android/content/pm/Checksum.java
index 123aaddda7ce..10ca5cac5276 100644
--- a/core/java/android/content/pm/Checksum.java
+++ b/core/java/android/content/pm/Checksum.java
@@ -32,14 +32,19 @@ import java.util.List;
*
* @see PackageInstaller.Session#addChecksums(String, List)
*/
-@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>.
*
+ * 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#getChecksums
+ * @see PackageInstaller.Session#addChecksums
*/
public static final int WHOLE_MERKLE_ROOT_4K_SHA256 = 0x00000001;
@@ -47,20 +52,31 @@ public final class Checksum implements Parcelable {
* MD5 hash computed over all file bytes.
*
* @see PackageManager#getChecksums
+ * @see PackageInstaller.Session#addChecksums
+ * @deprecated Use SHA2 family of hashes (SHA256/SHA512) instead.
+ * MD5 is cryptographically broken and unsuitable for further use.
+ * Provided for completeness' sake and to support legacy usecases.
*/
+ @Deprecated
public static final int WHOLE_MD5 = 0x00000002;
/**
* SHA1 hash computed over all file bytes.
*
* @see PackageManager#getChecksums
+ * @see PackageInstaller.Session#addChecksums
+ * @deprecated Use SHA2 family of hashes (SHA256/SHA512) instead.
+ * SHA1 is broken and should not be used.
+ * Provided for completeness' sake and to support legacy usecases.
*/
+ @Deprecated
public static final int WHOLE_SHA1 = 0x00000004;
/**
* SHA256 hash computed over all file bytes.
*
* @see PackageManager#getChecksums
+ * @see PackageInstaller.Session#addChecksums
*/
public static final int WHOLE_SHA256 = 0x00000008;
@@ -68,6 +84,7 @@ public final class Checksum implements Parcelable {
* SHA512 hash computed over all file bytes.
*
* @see PackageManager#getChecksums
+ * @see PackageInstaller.Session#addChecksums
*/
public static final int WHOLE_SHA512 = 0x00000010;
@@ -77,6 +94,7 @@ public final class Checksum implements Parcelable {
* <a href="https://source.android.com/security/apksigning/v2">See APK Signature Scheme V2</a>.
*
* @see PackageManager#getChecksums
+ * @see PackageInstaller.Session#addChecksums
*/
public static final int PARTIAL_MERKLE_ROOT_1M_SHA256 = 0x00000020;
@@ -86,6 +104,7 @@ public final class Checksum implements Parcelable {
* <a href="https://source.android.com/security/apksigning/v2">See APK Signature Scheme V2</a>.
*
* @see PackageManager#getChecksums
+ * @see PackageInstaller.Session#addChecksums
*/
public static final int PARTIAL_MERKLE_ROOT_1M_SHA512 = 0x00000040;
@@ -113,6 +132,7 @@ public final class Checksum implements Parcelable {
+
// Code below generated by codegen v1.0.15.
//
// DO NOT MODIFY!
@@ -133,7 +153,6 @@ public final class Checksum implements Parcelable {
* Checksum kind.
* @param value
* Checksum value.
- * @hide
*/
@DataClass.Generated.Member
public Checksum(
@@ -214,10 +233,10 @@ public final class Checksum implements Parcelable {
};
@DataClass.Generated(
- time = 1599845646883L,
+ time = 1600717052366L,
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 WHOLE_MERKLE_ROOT_4K_SHA256\npublic static final @java.lang.Deprecated int WHOLE_MD5\npublic static final @java.lang.Deprecated 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(genConstDefs=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 244cfe124381..af2b9056dc81 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -83,6 +83,11 @@ interface IPackageManager {
@UnsupportedAppUsage
ApplicationInfo getApplicationInfo(String packageName, int flags ,int userId);
+ /**
+ * @return the target SDK for the given package name, or -1 if it cannot be retrieved
+ */
+ int getTargetSdkVersion(String packageName);
+
@UnsupportedAppUsage
ActivityInfo getActivityInfo(in ComponentName className, int flags, int userId);
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 75ee9b7943fe..da56abf73516 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1227,7 +1227,10 @@ public class PackageInstaller {
* IntentSender)}.
* @throws SecurityException if called after the session has been
* committed or abandoned.
+ * @deprecated use platform-enforced checksums e.g.
+ * {@link Checksum#WHOLE_MERKLE_ROOT_4K_SHA256}
*/
+ @Deprecated
public void addChecksums(@NonNull String name, @NonNull List<Checksum> checksums)
throws IOException {
Objects.requireNonNull(name);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index e68df3383642..f0b2329385af 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4099,6 +4099,15 @@ public abstract class PackageManager {
}
/**
+ * @return The target SDK version for the given package name.
+ * @throws NameNotFoundException if a package with the given name cannot be found on the system.
+ */
+ @IntRange(from = 0)
+ public int getTargetSdkVersion(@NonNull String packageName) throws NameNotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
* Retrieve all of the information we know about a particular activity
* class.
*
@@ -7864,6 +7873,9 @@ public abstract class PackageManager {
* - 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
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/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/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 392f56212058..8f99edf61ece 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -841,6 +841,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 {
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/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/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 27bb9e20f968..60ddd8ab22a0 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -18,7 +18,6 @@ package android.inputmethodservice;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
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;
@@ -2201,22 +2200,15 @@ public class InputMethodService extends AbstractInputMethodService {
}
/**
- * Apply the IME visibility in {@link android.view.ImeInsetsSourceConsumer} when
- * {@link ViewRootImpl.sNewInsetsMode} is enabled.
+ * Applies the IME visibility in {@link android.view.ImeInsetsSourceConsumer}.
+ *
* @param setVisible {@code true} to make it visible, false to hide it.
*/
private void applyVisibilityInInsetsConsumerIfNecessary(boolean setVisible) {
- if (!isVisibilityAppliedUsingInsetsConsumer()) {
- return;
- }
mPrivOps.applyImeVisibility(setVisible
? mCurShowInputToken : mCurHideInputToken, setVisible);
}
- private boolean isVisibilityAppliedUsingInsetsConsumer() {
- return ViewRootImpl.sNewInsetsMode > NEW_INSETS_MODE_NONE;
- }
-
private void finishViews(boolean finishingInput) {
if (mInputViewStarted) {
if (DEBUG) Log.v(TAG, "CALL: onFinishInputView");
@@ -2241,14 +2233,9 @@ public class InputMethodService extends AbstractInputMethodService {
mWindowVisible = false;
finishViews(false /* finishingInput */);
if (mDecorViewVisible) {
- // When insets API is enabled, it is responsible for client and server side
- // visibility of IME window.
- if (isVisibilityAppliedUsingInsetsConsumer()) {
- if (mInputView != null) {
- mInputView.dispatchWindowVisibilityChanged(View.GONE);
- }
- } else {
- mWindow.hide();
+ // It is responsible for client and server side visibility of IME window.
+ if (mInputView != null) {
+ mInputView.dispatchWindowVisibilityChanged(View.GONE);
}
mDecorViewVisible = false;
onWindowHidden();
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/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index d80a7e794220..b15109e67086 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -34,6 +34,7 @@ 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;
@@ -543,10 +544,15 @@ 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);
}
}
@@ -679,11 +685,20 @@ 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/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
index 0cdad9f8de93..400b312bb913 100644
--- a/core/java/android/preference/SeekBarVolumizer.java
+++ b/core/java/android/preference/SeekBarVolumizer.java
@@ -46,6 +46,8 @@ import android.widget.SeekBar.OnSeekBarChangeListener;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.SomeArgs;
+import java.util.concurrent.TimeUnit;
+
/**
* Turns a {@link SeekBar} into a volume control.
* @hide
@@ -64,9 +66,15 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
void onSampleStarting(SeekBarVolumizer sbv);
void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch);
void onMuted(boolean muted, boolean zenMuted);
+ /**
+ * Callback reporting that the seek bar is start tracking.
+ * @param sbv - The seek bar that start tracking
+ */
+ void onStartTrackingTouch(SeekBarVolumizer sbv);
}
private static final int MSG_GROUP_VOLUME_CHANGED = 1;
+ private static long sStopVolumeTime = 0L;
private final Handler mVolumeHandler = new VolumeHandler();
private AudioAttributes mAttributes;
private int mVolumeGroupId;
@@ -126,6 +134,9 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
private static final int MSG_STOP_SAMPLE = 2;
private static final int MSG_INIT_SAMPLE = 3;
private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000;
+ private static final long SET_STREAM_VOLUME_DELAY_MS = TimeUnit.MILLISECONDS.toMillis(500);
+ private static final long START_SAMPLE_DELAY_MS = TimeUnit.MILLISECONDS.toMillis(500);
+ private static final long DURATION_TO_START_DELAYING = TimeUnit.MILLISECONDS.toMillis(2000);
private NotificationManager.Policy mNotificationPolicy;
private boolean mAllowAlarms;
@@ -314,7 +325,29 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
if (mHandler == null) return;
mHandler.removeMessages(MSG_START_SAMPLE);
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_SAMPLE),
- isSamplePlaying() ? CHECK_RINGTONE_PLAYBACK_DELAY_MS : 0);
+ isSamplePlaying() ? CHECK_RINGTONE_PLAYBACK_DELAY_MS
+ : isDelay() ? START_SAMPLE_DELAY_MS : 0);
+ }
+
+ // After stop volume it needs to add a small delay when playing volume or set stream.
+ // It is because the call volume is from the earpiece and the alarm/ring/media
+ // is from the speaker. If play the alarm volume or set alarm stream right after stop
+ // call volume, the alarm volume on earpiece is returned then cause the volume value incorrect.
+ // It needs a small delay after stop call volume to get alarm volume on speaker.
+ // e.g. : If the ring volume has adjusted right after call volume stopped in 2 second
+ // then delay 0.5 second to set stream or play volume ringtone.
+ private boolean isDelay() {
+ final long durationTime = java.lang.System.currentTimeMillis() - sStopVolumeTime;
+ return durationTime >= 0 && durationTime < DURATION_TO_START_DELAYING;
+ }
+
+ private void setStopVolumeTime() {
+ // set the time of stop volume
+ if ((mStreamType == AudioManager.STREAM_VOICE_CALL
+ || mStreamType == AudioManager.STREAM_RING
+ || mStreamType == AudioManager.STREAM_ALARM)) {
+ sStopVolumeTime = java.lang.System.currentTimeMillis();
+ }
}
private void onStartSample() {
@@ -341,6 +374,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
private void postStopSample() {
if (mHandler == null) return;
+ setStopVolumeTime();
// remove pending delayed start messages
mHandler.removeMessages(MSG_START_SAMPLE);
mHandler.removeMessages(MSG_STOP_SAMPLE);
@@ -404,10 +438,15 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
// Do the volume changing separately to give responsive UI
mLastProgress = progress;
mHandler.removeMessages(MSG_SET_STREAM_VOLUME);
- mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_STREAM_VOLUME));
+ mHandler.removeMessages(MSG_START_SAMPLE);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SET_STREAM_VOLUME),
+ isDelay() ? SET_STREAM_VOLUME_DELAY_MS : 0);
}
public void onStartTrackingTouch(SeekBar seekBar) {
+ if (mCallback != null) {
+ mCallback.onStartTrackingTouch(this);
+ }
}
public void onStopTrackingTouch(SeekBar seekBar) {
@@ -539,7 +578,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
if (AudioManager.VOLUME_CHANGED_ACTION.equals(action)) {
int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
int streamValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
- if (hasAudioProductStrategies()) {
+ if (hasAudioProductStrategies() && !isDelay()) {
updateVolumeSlider(streamType, streamValue);
}
} else if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) {
@@ -551,7 +590,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
}
} else if (AudioManager.STREAM_DEVICES_CHANGED_ACTION.equals(action)) {
int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
- if (hasAudioProductStrategies()) {
+ if (hasAudioProductStrategies() && !isDelay()) {
int streamVolume = mAudioManager.getStreamVolume(streamType);
updateVolumeSlider(streamType, streamVolume);
} else {
@@ -559,7 +598,9 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
if (volumeGroup != AudioVolumeGroup.DEFAULT_VOLUME_GROUP
&& volumeGroup == mVolumeGroupId) {
int streamVolume = mAudioManager.getStreamVolume(streamType);
- updateVolumeSlider(streamType, streamVolume);
+ if (!isDelay()) {
+ updateVolumeSlider(streamType, streamVolume);
+ }
}
}
} else if (NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED.equals(action)) {
diff --git a/core/java/android/preference/VolumePreference.java b/core/java/android/preference/VolumePreference.java
index 6eb524a3886c..563bc463e5a3 100644
--- a/core/java/android/preference/VolumePreference.java
+++ b/core/java/android/preference/VolumePreference.java
@@ -176,6 +176,11 @@ public class VolumePreference extends SeekBarDialogPreference implements
}
@Override
+ public void onStartTrackingTouch(SeekBarVolumizer sbv) {
+ // noop
+ }
+
+ @Override
protected Parcelable onSaveInstanceState() {
final Parcelable superState = super.onSaveInstanceState();
if (isPersistent()) {
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/Settings.java b/core/java/android/provider/Settings.java
index 18337b661844..8a5963ed02eb 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11808,29 +11808,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:
*
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 9f52142a5109..79d6bb4a062a 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -3956,9 +3956,7 @@ public final class Telephony {
public static final String APN_SET_ID = "apn_set_id";
/**
- * Possible value for the {@link #APN_SET_ID} field. By default APNs will not belong to a
- * set. If the user manually selects an APN without apn set id, there is no need to
- * prioritize any specific APN set ids.
+ * Possible value for the {@link #APN_SET_ID} field. By default APNs are added to set 0.
* <p>Type: INTEGER</p>
* @hide
*/
@@ -3966,6 +3964,16 @@ public final class Telephony {
public static final int NO_APN_SET_ID = 0;
/**
+ * Possible value for the {@link #APN_SET_ID} field.
+ * APNs with MATCH_ALL_APN_SET_ID will be used regardless of any set ids of
+ * the selected APN.
+ * <p>Type: INTEGER</p>
+ * @hide
+ */
+ @SystemApi
+ public static final int MATCH_ALL_APN_SET_ID = -1;
+
+ /**
* A unique carrier id associated with this APN
* {@see TelephonyManager#getSimCarrierId()}
* <p>Type: STRING</p>
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 0827fef60252..49bc65b3c8e5 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -1326,7 +1326,8 @@ public class ZenModeConfig implements Parcelable {
line2 = res.getString(R.string.zen_mode_until, formattedTime);
} else {
// display as day/time
- summary = line1 = line2 = res.getString(R.string.zen_mode_until, formattedTime);
+ summary = line1 = line2 = res.getString(R.string.zen_mode_until_next_day,
+ formattedTime);
}
final Uri id = toCountdownConditionId(time, false);
return new Condition(id, summary, line1, line2, 0, Condition.STATE_TRUE,
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/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index 8f31d77ac180..16826865987e 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -1001,16 +1001,43 @@ public class PhoneStateListener {
/**
* Callback invoked when an outgoing SMS is placed to an emergency number.
*
+ * This method will be called when an emergency sms is sent on any subscription.
* @param sentEmergencyNumber the emergency number {@link EmergencyNumber} the SMS is sent to.
+ *
+ * @deprecated Use {@link #onOutgoingEmergencySms(EmergencyNumber, int)}.
* @hide
*/
@SystemApi
@TestApi
+ @Deprecated
public void onOutgoingEmergencySms(@NonNull EmergencyNumber sentEmergencyNumber) {
// default implementation empty
}
/**
+ * Smsback invoked when an outgoing sms is sent to an emergency number.
+ *
+ * This method will be called when an emergency sms is sent on any subscription,
+ * regardless of which subscription this listener was registered on.
+ *
+ * The default implementation of this method calls
+ * {@link #onOutgoingEmergencySms(EmergencyNumber)} for backwards compatibility purposes. Do
+ * not call {@code super(...)} from within your implementation unless you want
+ * {@link #onOutgoingEmergencySms(EmergencyNumber)} to be called as well.
+ *
+ * @param sentEmergencyNumber The {@link EmergencyNumber} the emergency sms was sent to.
+ * @param subscriptionId The subscription ID used to send the emergency sms.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public void onOutgoingEmergencySms(@NonNull EmergencyNumber sentEmergencyNumber,
+ int subscriptionId) {
+ // Default implementation for backwards compatibility
+ onOutgoingEmergencySms(sentEmergencyNumber);
+ }
+
+ /**
* Callback invoked when OEM hook raw event is received on the registered subscription.
* Note, the registration subId comes from {@link TelephonyManager} object which registers
* PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
@@ -1399,13 +1426,14 @@ public class PhoneStateListener {
subscriptionId)));
}
- public void onOutgoingEmergencySms(@NonNull EmergencyNumber sentEmergencyNumber) {
+ public void onOutgoingEmergencySms(@NonNull EmergencyNumber sentEmergencyNumber,
+ int subscriptionId) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(
- () -> psl.onOutgoingEmergencySms(sentEmergencyNumber)));
+ () -> psl.onOutgoingEmergencySms(sentEmergencyNumber, subscriptionId)));
}
public void onPhoneCapabilityChanged(PhoneCapability capability) {
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 6d48dc36ceb4..24ed29a66654 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -101,6 +101,10 @@ public class TelephonyRegistryManager {
public void addOnSubscriptionsChangedListener(
@NonNull SubscriptionManager.OnSubscriptionsChangedListener listener,
@NonNull Executor executor) {
+ if (mSubscriptionChangedListenerMap.get(listener) != null) {
+ Log.d(TAG, "addOnSubscriptionsChangedListener listener already present");
+ return;
+ }
IOnSubscriptionsChangedListener callback = new IOnSubscriptionsChangedListener.Stub() {
@Override
public void onSubscriptionsChanged () {
@@ -150,6 +154,10 @@ public class TelephonyRegistryManager {
public void addOnOpportunisticSubscriptionsChangedListener(
@NonNull SubscriptionManager.OnOpportunisticSubscriptionsChangedListener listener,
@NonNull Executor executor) {
+ if (mOpportunisticSubscriptionChangedListenerMap.get(listener) != null) {
+ Log.d(TAG, "addOnOpportunisticSubscriptionsChangedListener listener already present");
+ return;
+ }
/**
* The callback methods need to be called on the executor thread where
* this object was created. If the binder did that for us it'd be nice.
@@ -185,6 +193,9 @@ public class TelephonyRegistryManager {
*/
public void removeOnOpportunisticSubscriptionsChangedListener(
@NonNull SubscriptionManager.OnOpportunisticSubscriptionsChangedListener listener) {
+ if (mOpportunisticSubscriptionChangedListenerMap.get(listener) == null) {
+ return;
+ }
try {
sRegistry.removeOnSubscriptionsChangedListener(mContext.getOpPackageName(),
mOpportunisticSubscriptionChangedListenerMap.get(listener));
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 9cc6b9f83ede..9244647432c7 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -44,6 +44,8 @@ public class FeatureFlagUtils {
/** @hide */
public static final String SETTINGS_DO_NOT_RESTORE_PRESERVED =
"settings_do_not_restore_preserved";
+ /** @hide */
+ public static final String SETTINGS_PROVIDER_MODEL = "settings_provider_model";
private static final Map<String, String> DEFAULT_FLAGS;
@@ -67,6 +69,7 @@ public class FeatureFlagUtils {
DEFAULT_FLAGS.put("settings_tether_all_in_one", "false");
DEFAULT_FLAGS.put("settings_silky_home", "false");
DEFAULT_FLAGS.put("settings_contextual_home", "false");
+ DEFAULT_FLAGS.put(SETTINGS_PROVIDER_MODEL, "false");
}
/**
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 987edf7761d2..042808a4cad2 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -660,8 +660,7 @@ public final class Choreographer {
ThreadedRenderer.setFPSDivisor(divisor);
}
- @UnsupportedAppUsage
- void doFrame(long frameTimeNanos, int frame) {
+ void doFrame(long frameTimeNanos, int frame, long frameTimelineVsyncId) {
final long startNanos;
synchronized (mLock) {
if (!mFrameScheduled) {
@@ -711,7 +710,7 @@ public final class Choreographer {
}
}
- mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
+ mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, frameTimelineVsyncId);
mFrameScheduled = false;
mLastFrameTimeNanos = frameTimeNanos;
}
@@ -897,7 +896,7 @@ public final class Choreographer {
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DO_FRAME:
- doFrame(System.nanoTime(), 0);
+ doFrame(System.nanoTime(), 0, FrameInfo.INVALID_VSYNC_ID);
break;
case MSG_DO_SCHEDULE_VSYNC:
doScheduleVsync();
@@ -914,6 +913,7 @@ public final class Choreographer {
private boolean mHavePendingVsync;
private long mTimestampNanos;
private int mFrame;
+ private long mFrameTimelineVsyncId;
public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
super(looper, vsyncSource, CONFIG_CHANGED_EVENT_SUPPRESS);
@@ -923,7 +923,8 @@ public final class Choreographer {
// the internal display and DisplayEventReceiver#scheduleVsync only allows requesting VSYNC
// for the internal display implicitly.
@Override
- public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
+ public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
+ long frameTimelineVsyncId) {
// 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
@@ -946,6 +947,7 @@ public final class Choreographer {
mTimestampNanos = timestampNanos;
mFrame = frame;
+ mFrameTimelineVsyncId = frameTimelineVsyncId;
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
@@ -954,7 +956,7 @@ public final class Choreographer {
@Override
public void run() {
mHavePendingVsync = false;
- doFrame(mTimestampNanos, mFrame);
+ doFrame(mTimestampNanos, mFrame, mFrameTimelineVsyncId);
}
}
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index eaf297cc05d8..51474d3c39c8 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -154,9 +154,11 @@ 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.
*/
- @UnsupportedAppUsage
- public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
+ public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
+ long frameTimelineVsyncId) {
}
/**
@@ -198,9 +200,9 @@ public abstract class DisplayEventReceiver {
// Called from native code.
@SuppressWarnings("unused")
- @UnsupportedAppUsage
- private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) {
- onVsync(timestampNanos, physicalDisplayId, frame);
+ private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame,
+ long frameTimelineVsyncId) {
+ onVsync(timestampNanos, physicalDisplayId, frame, frameTimelineVsyncId);
}
// Called from native code.
diff --git a/core/java/android/view/FrameMetrics.java b/core/java/android/view/FrameMetrics.java
index 32cc30be8de4..280a1c0d95d2 100644
--- a/core/java/android/view/FrameMetrics.java
+++ b/core/java/android/view/FrameMetrics.java
@@ -189,6 +189,7 @@ public final class FrameMetrics {
*/
@IntDef ({
Index.FLAGS,
+ Index.FRAME_TIMELINE_VSYNC_ID,
Index.INTENDED_VSYNC,
Index.VSYNC,
Index.OLDEST_INPUT_EVENT,
@@ -206,21 +207,22 @@ public final class FrameMetrics {
@Retention(RetentionPolicy.SOURCE)
private @interface Index {
int FLAGS = 0;
- int INTENDED_VSYNC = 1;
- int VSYNC = 2;
- int OLDEST_INPUT_EVENT = 3;
- int NEWEST_INPUT_EVENT = 4;
- int HANDLE_INPUT_START = 5;
- int ANIMATION_START = 6;
- int PERFORM_TRAVERSALS_START = 7;
- int DRAW_START = 8;
- int SYNC_QUEUED = 9;
- int SYNC_START = 10;
- int ISSUE_DRAW_COMMANDS_START = 11;
- int SWAP_BUFFERS = 12;
- int FRAME_COMPLETED = 13;
+ int FRAME_TIMELINE_VSYNC_ID = 1;
+ int INTENDED_VSYNC = 2;
+ int VSYNC = 3;
+ int OLDEST_INPUT_EVENT = 4;
+ int NEWEST_INPUT_EVENT = 5;
+ int HANDLE_INPUT_START = 6;
+ 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_STATS_COUNT = 17; // must always be last
+ int FRAME_STATS_COUNT = 18; // must always be last
}
/*
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 609f80253ce6..18c87c092569 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -121,7 +121,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 +134,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 +146,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 +157,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 +192,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 +230,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 +276,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 +292,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 +303,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 +334,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/InsetsState.java b/core/java/android/view/InsetsState.java
index 373a0963dc2f..f462a990e958 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -17,10 +17,6 @@
package android.view;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
-import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
-import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME;
-import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
-import static android.view.ViewRootImpl.sNewInsetsMode;
import static android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES;
import static android.view.WindowInsets.Type.SYSTEM_GESTURES;
import static android.view.WindowInsets.Type.displayCutout;
@@ -195,18 +191,6 @@ public class InsetsState implements Parcelable {
continue;
}
- boolean skipNonImeInImeMode = ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_IME
- && source.getType() != ITYPE_IME;
- boolean skipSystemBars = ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL
- && (type == ITYPE_STATUS_BAR || type == ITYPE_NAVIGATION_BAR);
- boolean skipLegacyTypes = ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE
- && (type == ITYPE_STATUS_BAR || type == ITYPE_NAVIGATION_BAR
- || type == ITYPE_IME);
- if (skipSystemBars || skipLegacyTypes || skipNonImeInImeMode) {
- typeVisibilityMap[indexOf(toPublicType(type))] = source.isVisible();
- continue;
- }
-
processSource(source, relativeFrame, false /* ignoreVisibility */, typeInsetsMap,
typeSideMap, typeVisibilityMap);
@@ -239,8 +223,7 @@ public class InsetsState implements Parcelable {
return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound,
alwaysConsumeSystemBars, cutout, compatInsetsTypes,
- sNewInsetsMode == NEW_INSETS_MODE_FULL
- && (legacySystemUiFlags & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0);
+ (legacySystemUiFlags & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0);
}
public Rect calculateVisibleInsets(Rect frame, @SoftInputModeFlags int softInputMode) {
@@ -250,9 +233,6 @@ public class InsetsState implements Parcelable {
if (source == null) {
continue;
}
- if (sNewInsetsMode != NEW_INSETS_MODE_FULL && type != ITYPE_IME) {
- continue;
- }
// Ignore everything that's not a system bar or IME.
int publicType = InsetsState.toPublicType(type);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 92a0f633aba0..e7e28ac98234 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -17,7 +17,6 @@
package android.view;
import static android.content.res.Resources.ID_NULL;
-import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS;
@@ -5435,8 +5434,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
sAcceptZeroSizeDragShadow = targetSdkVersion < Build.VERSION_CODES.P;
- sBrokenInsetsDispatch = ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL
- || targetSdkVersion < Build.VERSION_CODES.R;
+ sBrokenInsetsDispatch = targetSdkVersion < Build.VERSION_CODES.R;
sBrokenWindowBackground = targetSdkVersion < Build.VERSION_CODES.Q;
@@ -26263,6 +26261,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * This needs to be a better API before it is exposed. For now, only the root view will get
+ * notified.
+ * @hide
+ */
+ public void onSystemBarAppearanceChanged(@WindowInsetsController.Appearance int appearance) {
+ }
+
+ /**
* Creates an image that the system displays during the drag and drop
* operation. This is called a &quot;drag shadow&quot;. The default implementation
* for a DragShadowBuilder based on a View returns an image that has exactly the same
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e6f89a761e6b..0bbd8992bc1a 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -221,45 +221,6 @@ public final class ViewRootImpl implements ViewParent,
private static final boolean MT_RENDERER_AVAILABLE = true;
/**
- * If set to 2, the view system will switch from using rectangles retrieved from window to
- * dispatch to the view hierarchy to using {@link InsetsController}, that derives the insets
- * directly from the full configuration, enabling richer information about the insets state, as
- * well as new APIs to control it frame-by-frame, and synchronize animations with it.
- * <p>
- * Only set this to 2 once the new insets system is productionized and the old APIs are
- * fully migrated over.
- * <p>
- * If set to 1, this will switch to a mode where we only use the new approach for IME, but not
- * for the status/navigation bar.
- */
- private static final String USE_NEW_INSETS_PROPERTY = "persist.debug.new_insets";
-
- /**
- * @see #USE_NEW_INSETS_PROPERTY
- * @hide
- */
- public static final int NEW_INSETS_MODE_NONE = 0;
-
- /**
- * @see #USE_NEW_INSETS_PROPERTY
- * @hide
- */
- public static final int NEW_INSETS_MODE_IME = 1;
-
- /**
- * @see #USE_NEW_INSETS_PROPERTY
- * @hide
- */
- public static final int NEW_INSETS_MODE_FULL = 2;
-
- /**
- * @see #USE_NEW_INSETS_PROPERTY
- * @hide
- */
- public static int sNewInsetsMode =
- SystemProperties.getInt(USE_NEW_INSETS_PROPERTY, NEW_INSETS_MODE_FULL);
-
- /**
* Set this system property to true to force the view hierarchy to render
* at 60 Hz. This can be used to measure the potential framerate.
*/
@@ -454,8 +415,8 @@ public final class ViewRootImpl implements ViewParent,
@UnsupportedAppUsage
final View.AttachInfo mAttachInfo;
final SystemUiVisibilityInfo mCompatibleVisibilityInfo;
- int mDispatchedSystemUiVisibility =
- ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_FULL ? 0 : -1;
+ int mDispatchedSystemUiVisibility;
+ int mDispatchedSystemBarAppearance;
InputQueue.Callback mInputQueueCallback;
InputQueue mInputQueue;
@UnsupportedAppUsage
@@ -1409,6 +1370,10 @@ public final class ViewRootImpl implements ViewParent,
attrs.systemUiVisibility = mWindowAttributes.systemUiVisibility;
attrs.subtreeSystemUiVisibility = mWindowAttributes.subtreeSystemUiVisibility;
+ // Transfer over appearance and behavior values as they carry current state.
+ attrs.insetsFlags.appearance = mWindowAttributes.insetsFlags.appearance;
+ attrs.insetsFlags.behavior = mWindowAttributes.insetsFlags.behavior;
+
final int changes = mWindowAttributes.copyFrom(attrs);
if ((changes & WindowManager.LayoutParams.TRANSLUCENT_FLAGS_CHANGED) != 0) {
// Recompute system ui visibility.
@@ -1627,9 +1592,6 @@ public final class ViewRootImpl implements ViewParent,
}
void notifyInsetsChanged() {
- if (sNewInsetsMode == NEW_INSETS_MODE_NONE) {
- return;
- }
mApplyInsetsRequested = true;
requestLayout();
@@ -2063,8 +2025,7 @@ public final class ViewRootImpl implements ViewParent,
*/
void updateCompatSysUiVisibility(@InternalInsetsType int type, boolean visible,
boolean hasControl) {
- if ((type != ITYPE_STATUS_BAR && type != ITYPE_NAVIGATION_BAR)
- || ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL) {
+ if (type != ITYPE_STATUS_BAR && type != ITYPE_NAVIGATION_BAR) {
return;
}
final SystemUiVisibilityInfo info = mCompatibleVisibilityInfo;
@@ -2089,14 +2050,6 @@ public final class ViewRootImpl implements ViewParent,
}
private void handleDispatchSystemUiVisibilityChanged(SystemUiVisibilityInfo args) {
- if (mSeq != args.seq && sNewInsetsMode != NEW_INSETS_MODE_FULL) {
- // The sequence has changed, so we need to update our value and make
- // sure to do a traversal afterward so the window manager is given our
- // most recent data.
- mSeq = args.seq;
- mAttachInfo.mForceReportNewAttributes = true;
- scheduleTraversals();
- }
if (mView == null) return;
if (args.localChanges != 0) {
mView.updateLocalSystemUiVisibility(args.localValue, args.localChanges);
@@ -2112,9 +2065,6 @@ public final class ViewRootImpl implements ViewParent,
@VisibleForTesting
public static void adjustLayoutParamsForCompatibility(WindowManager.LayoutParams inOutParams) {
- if (sNewInsetsMode != NEW_INSETS_MODE_FULL) {
- return;
- }
final int sysUiVis = inOutParams.systemUiVisibility | inOutParams.subtreeSystemUiVisibility;
final int flags = inOutParams.flags;
final int type = inOutParams.type;
@@ -2179,9 +2129,6 @@ public final class ViewRootImpl implements ViewParent,
}
private void controlInsetsForCompatibility(WindowManager.LayoutParams params) {
- if (sNewInsetsMode != NEW_INSETS_MODE_FULL) {
- return;
- }
final int sysUiVis = params.systemUiVisibility | params.subtreeSystemUiVisibility;
final int flags = params.flags;
final boolean matchParent = params.width == MATCH_PARENT && params.height == MATCH_PARENT;
@@ -2629,6 +2576,10 @@ public final class ViewRootImpl implements ViewParent,
}
adjustLayoutParamsForCompatibility(params);
controlInsetsForCompatibility(params);
+ if (mDispatchedSystemBarAppearance != params.insetsFlags.appearance) {
+ mDispatchedSystemBarAppearance = params.insetsFlags.appearance;
+ mView.onSystemBarAppearanceChanged(mDispatchedSystemBarAppearance);
+ }
}
if (mFirst || windowShouldResize || viewVisibilityChanged || cutoutChanged || params != null
@@ -3239,14 +3190,12 @@ public final class ViewRootImpl implements ViewParent,
hasWindowFocus = mUpcomingWindowFocus;
inTouchMode = mUpcomingInTouchMode;
}
- if (sNewInsetsMode != NEW_INSETS_MODE_NONE) {
- // TODO (b/131181940): Make sure this doesn't leak Activity with mActivityConfigCallback
- // config changes.
- if (hasWindowFocus) {
- mInsetsController.onWindowFocusGained();
- } else {
- mInsetsController.onWindowFocusLost();
- }
+ // TODO (b/131181940): Make sure this doesn't leak Activity with mActivityConfigCallback
+ // config changes.
+ if (hasWindowFocus) {
+ mInsetsController.onWindowFocusGained();
+ } else {
+ mInsetsController.onWindowFocusLost();
}
if (mAdded) {
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 5d53ad7ef186..f57ee65948c0 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -19,7 +19,6 @@ package android.view;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
-import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
@@ -30,7 +29,6 @@ import android.app.ResourcesManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Configuration;
-import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Bundle;
@@ -275,19 +273,10 @@ public final class WindowManagerImpl implements WindowManager {
final Configuration config = mContext.getResources().getConfiguration();
final boolean isScreenRound = config.isScreenRound();
final int windowingMode = config.windowConfiguration.getWindowingMode();
- if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_FULL) {
- return insetsState.calculateInsets(bounds, null /* ignoringVisibilityState*/,
- isScreenRound, alwaysConsumeSystemBars, displayCutout.get(),
- SOFT_INPUT_ADJUST_NOTHING, attrs.flags, SYSTEM_UI_FLAG_VISIBLE, attrs.type,
- windowingMode, null /* typeSideMap */);
- } else {
- return new WindowInsets.Builder()
- .setAlwaysConsumeSystemBars(alwaysConsumeSystemBars)
- .setRound(isScreenRound)
- .setSystemWindowInsets(Insets.of(systemWindowInsets))
- .setStableInsets(Insets.of(stableInsets))
- .setDisplayCutout(displayCutout.get()).build();
- }
+ return insetsState.calculateInsets(bounds, null /* ignoringVisibilityState*/,
+ isScreenRound, alwaysConsumeSystemBars, displayCutout.get(),
+ SOFT_INPUT_ADJUST_NOTHING, attrs.flags, SYSTEM_UI_FLAG_VISIBLE, attrs.type,
+ windowingMode, null /* typeSideMap */);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index c5f2299e4f83..104bc4347c29 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -22,13 +22,14 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.LocaleList;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
-import android.text.Editable;
import android.text.InputType;
+import android.text.ParcelableSpan;
import android.text.TextUtils;
import android.util.Printer;
import android.view.View;
@@ -543,6 +544,9 @@ public class EditorInfo implements InputType, Parcelable {
* {@code #getInitialSelectedText}, and {@code #getInitialTextBeforeCursor}. System is allowed
* to trim {@code sourceText} for various reasons while keeping the most valuable data to IMEs.
*
+ * Starting from {@link VERSION_CODES#S}, spans that do not implement {@link Parcelable} will
+ * be automatically dropped.
+ *
* <p><strong>Editor authors: </strong>Providing the initial input text helps reducing IPC calls
* for IMEs to provide many modern features right after the connection setup. We recommend
* calling this method in your implementation.
@@ -562,14 +566,16 @@ public class EditorInfo implements InputType, Parcelable {
* try to include the selected text within {@code subText} to give the system best flexibility
* to choose where and how to trim {@code subText} when necessary.
*
+ * Starting from {@link VERSION_CODES#S}, spans that do not implement {@link Parcelable} will
+ * be automatically dropped.
+ *
* @param subText The input text. When it was trimmed, {@code subTextStart} must be provided
* correctly.
* @param subTextStart The position that the input text got trimmed. For example, when the
* editor wants to trim out the first 10 chars, subTextStart should be 10.
*/
public void setInitialSurroundingSubText(@NonNull CharSequence subText, int subTextStart) {
- CharSequence newSubText = Editable.Factory.getInstance().newEditable(subText);
- Objects.requireNonNull(newSubText);
+ Objects.requireNonNull(subText);
// Swap selection start and end if necessary.
final int subTextSelStart = initialSelStart > initialSelEnd
@@ -577,7 +583,7 @@ public class EditorInfo implements InputType, Parcelable {
final int subTextSelEnd = initialSelStart > initialSelEnd
? initialSelStart - subTextStart : initialSelEnd - subTextStart;
- final int subTextLength = newSubText.length();
+ final int subTextLength = subText.length();
// Unknown or invalid selection.
if (subTextStart < 0 || subTextSelStart < 0 || subTextSelEnd > subTextLength) {
mInitialSurroundingText = new InitialSurroundingText();
@@ -591,12 +597,12 @@ public class EditorInfo implements InputType, Parcelable {
}
if (subTextLength <= MEMORY_EFFICIENT_TEXT_LENGTH) {
- mInitialSurroundingText = new InitialSurroundingText(newSubText, subTextSelStart,
+ mInitialSurroundingText = new InitialSurroundingText(subText, subTextSelStart,
subTextSelEnd);
return;
}
- trimLongSurroundingText(newSubText, subTextSelStart, subTextSelEnd);
+ trimLongSurroundingText(subText, subTextSelStart, subTextSelEnd);
}
/**
@@ -901,7 +907,9 @@ public class EditorInfo implements InputType, Parcelable {
InitialSurroundingText(@Nullable CharSequence surroundingText, int selectionHead,
int selectionEnd) {
- mSurroundingText = surroundingText;
+ // Copy the original text (without NoCopySpan) in case the original text is updated
+ // later.
+ mSurroundingText = copyWithParcelableSpans(surroundingText);
mSelectionHead = selectionHead;
mSelectionEnd = selectionEnd;
}
@@ -975,5 +983,30 @@ public class EditorInfo implements InputType, Parcelable {
return new InitialSurroundingText[size];
}
};
+
+ /**
+ * Create a copy of the given {@link CharSequence} object, with completely copy
+ * {@link ParcelableSpan} instances.
+ *
+ * @param source the original {@link CharSequence} to be copied.
+ * @return the copied {@link CharSequence}. {@code null} if {@code source} is {@code null}.
+ */
+ @Nullable
+ private static CharSequence copyWithParcelableSpans(@Nullable CharSequence source) {
+ if (source == null) {
+ return null;
+ }
+ Parcel parcel = null;
+ try {
+ parcel = Parcel.obtain();
+ TextUtils.writeToParcel(source, parcel, /* parcelableFlags= */ 0);
+ parcel.setDataPosition(0);
+ return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ } finally {
+ if (parcel != null) {
+ parcel.recycle();
+ }
+ }
+ }
}
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index f6671d86cf7b..a3c95a916669 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -457,9 +457,7 @@ public final class InputMethodManager {
private int mRequestUpdateCursorAnchorInfoMonitorMode = REQUEST_UPDATE_CURSOR_ANCHOR_INFO_NONE;
/**
- * When {@link ViewRootImpl#sNewInsetsMode} is set to
- * >= {@link ViewRootImpl#NEW_INSETS_MODE_IME}, {@link ImeInsetsSourceConsumer} applies the
- * IME visibility and listens for other state changes.
+ * Applies the IME visibility and listens for other state changes.
*/
private ImeInsetsSourceConsumer mImeInsetsConsumer;
diff --git a/core/java/android/window/WindowMetricsHelper.java b/core/java/android/window/WindowMetricsHelper.java
index fb8b27e52be1..170ff6fb1a10 100644
--- a/core/java/android/window/WindowMetricsHelper.java
+++ b/core/java/android/window/WindowMetricsHelper.java
@@ -16,17 +16,13 @@
package android.window;
-import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
import static android.view.WindowInsets.Type.displayCutout;
import static android.view.WindowInsets.Type.navigationBars;
import android.annotation.NonNull;
-import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
import android.view.Display;
-import android.view.DisplayCutout;
-import android.view.ViewRootImpl;
import android.view.WindowInsets;
import android.view.WindowMetrics;
@@ -46,19 +42,8 @@ public final class WindowMetricsHelper {
public static Rect getBoundsExcludingNavigationBarAndCutout(
@NonNull WindowMetrics windowMetrics) {
final WindowInsets windowInsets = windowMetrics.getWindowInsets();
- Insets insets;
- if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_FULL) {
- insets = windowInsets.getInsetsIgnoringVisibility(navigationBars() | displayCutout());
- } else {
- final Insets stableInsets = windowInsets.getStableInsets();
- insets = Insets.of(stableInsets.left, 0 /* top */, stableInsets.right,
- stableInsets.bottom);
- final DisplayCutout cutout = windowInsets.getDisplayCutout();
- insets = (cutout != null) ? Insets.max(insets, Insets.of(cutout.getSafeInsets()))
- : insets;
- }
final Rect result = new Rect(windowMetrics.getBounds());
- result.inset(insets);
+ result.inset(windowInsets.getInsetsIgnoringVisibility(navigationBars() | displayCutout()));
return result;
}
}
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/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 6314fcbde333..1ce246ad4274 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -419,6 +419,17 @@ public final class SystemUiDeviceConfigFlags {
*/
public static final String SCREENSHOT_KEYCHORD_DELAY = "screenshot_keychord_delay";
+ /**
+ * (boolean) Whether to use an ML model for the Back Gesture.
+ */
+ public static final String USE_BACK_GESTURE_ML_MODEL = "use_back_gesture_ml_model";
+
+ /**
+ * (float) Threshold for Back Gesture ML model prediction.
+ */
+ public static final String BACK_GESTURE_ML_MODEL_THRESHOLD = "back_gesture_ml_model_threshold";
+
+
private SystemUiDeviceConfigFlags() {
}
}
diff --git a/core/java/com/android/internal/infra/AbstractRemoteService.java b/core/java/com/android/internal/infra/AbstractRemoteService.java
index b2852eaeef2a..722e5c102fcf 100644
--- a/core/java/com/android/internal/infra/AbstractRemoteService.java
+++ b/core/java/com/android/internal/infra/AbstractRemoteService.java
@@ -225,6 +225,7 @@ public abstract class AbstractRemoteService<S extends AbstractRemoteService<S, I
if (mService != null) {
mService.asBinder().unlinkToDeath(this, 0);
}
+ mBinding = false;
mService = null;
mServiceDied = true;
cancelScheduledUnbind();
diff --git a/core/java/com/android/internal/listeners/ListenerExecutor.java b/core/java/com/android/internal/listeners/ListenerExecutor.java
index 9979e6056f50..7d9b9c96b3a0 100644
--- a/core/java/com/android/internal/listeners/ListenerExecutor.java
+++ b/core/java/com/android/internal/listeners/ListenerExecutor.java
@@ -38,71 +38,86 @@ public interface ListenerExecutor {
void operate(TListener listener) throws Exception;
/**
- * Called before this operation is to be run. Some operations may be canceled before they
- * are run, in which case this method may not be called. {@link #onPostExecute(boolean)}
- * will only be run if this method was run. This callback is invoked on the calling thread.
+ * Called before this operation is to be run. An operation may be canceled before it is run,
+ * in which case this method may not be invoked. {@link #onPostExecute(boolean)} will only
+ * be invoked if this method was previously invoked. This callback is invoked on the
+ * calling thread.
*/
default void onPreExecute() {}
/**
* Called if the operation fails while running. Will not be invoked in the event of a
- * RuntimeException, which will propagate normally. Implementations of
- * {@link ListenerExecutor} have the option to override
- * {@link ListenerExecutor#onOperationFailure(ListenerOperation, Exception)} instead to
- * intercept failures at the class level. This callback is invoked on the executor thread.
+ * unchecked exception, which will propagate normally. This callback is invoked on the
+ * executor thread.
*/
- default void onFailure(Exception e) {
- // implementations should handle any exceptions that may be thrown
- throw new AssertionError(e);
- }
+ default void onFailure(Exception e) {}
/**
- * Called after the operation is run. This method will always be called if
- * {@link #onPreExecute()} is called. Success implies that the operation was run to
- * completion with no failures. This callback may be invoked on the calling thread or
- * executor thread.
+ * Called after the operation may have been run. Will always be invoked for every operation
+ * which has previously had {@link #onPreExecute()} invoked. Success implies that the
+ * operation was run to completion with no failures. If {@code success} is true, this
+ * callback will always be invoked on the executor thread. If {@code success} is false, this
+ * callback may be invoked on the calling thread or executor thread.
*/
default void onPostExecute(boolean success) {}
/**
- * Called after this operation is complete (which does not imply that it was necessarily
- * run). Will always be called once per operation, no matter if the operation was run or
- * not. Success implies that the operation was run to completion with no failures. This
- * callback may be invoked on the calling thread or executor thread.
+ * Will always be called once for every operation submitted to
+ * {@link #executeSafely(Executor, Supplier, ListenerOperation)}, no matter if the operation
+ * was run or not. This method is always invoked last, after every other callback. Success
+ * implies that the operation was run to completion with no failures. If {@code success}
+ * is true, this callback will always be invoked on the executor thread. If {@code success}
+ * is false, this callback may be invoked on the calling thread or executor thread.
*/
default void onComplete(boolean success) {}
}
/**
- * May be override to handle operation failures at a class level. Will not be invoked in the
- * event of a RuntimeException, which will propagate normally. This callback is invoked on the
- * executor thread.
+ * An callback for listener operation failure.
+ *
+ * @param <TListenerOperation> listener operation type
+ */
+ interface FailureCallback<TListenerOperation extends ListenerOperation<?>> {
+
+ /**
+ * Called if a listener operation fails while running with a checked exception. This
+ * callback is invoked on the executor thread.
+ */
+ void onFailure(TListenerOperation operation, Exception exception);
+ }
+
+ /**
+ * See {@link #executeSafely(Executor, Supplier, ListenerOperation, FailureCallback)}.
*/
- default <TListener> void onOperationFailure(ListenerOperation<TListener> operation,
- Exception exception) {
- operation.onFailure(exception);
+ default <TListener> void executeSafely(Executor executor, Supplier<TListener> listenerSupplier,
+ @Nullable ListenerOperation<TListener> operation) {
+ executeSafely(executor, listenerSupplier, operation, null);
}
/**
* Executes the given listener operation on the given executor, using the provided listener
* supplier. If the supplier returns a null value, or a value during the operation that does not
* match the value prior to the operation, then the operation is considered canceled. If a null
- * operation is supplied, nothing happens.
+ * operation is supplied, nothing happens. If a failure callback is supplied, this will be
+ * invoked on the executor thread in the event a checked exception is thrown from the listener
+ * operation.
*/
- default <TListener> void executeSafely(Executor executor, Supplier<TListener> listenerSupplier,
- @Nullable ListenerOperation<TListener> operation) {
+ default <TListener, TListenerOperation extends ListenerOperation<TListener>> void executeSafely(
+ Executor executor, Supplier<TListener> listenerSupplier,
+ @Nullable TListenerOperation operation,
+ @Nullable FailureCallback<TListenerOperation> failureCallback) {
if (operation == null) {
return;
}
+ TListener listener = listenerSupplier.get();
+ if (listener == null) {
+ return;
+ }
+
boolean executing = false;
boolean preexecute = false;
try {
- TListener listener = listenerSupplier.get();
- if (listener == null) {
- return;
- }
-
operation.onPreExecute();
preexecute = true;
executor.execute(() -> {
@@ -116,7 +131,10 @@ public interface ListenerExecutor {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
} else {
- onOperationFailure(operation, e);
+ operation.onFailure(e);
+ if (failureCallback != null) {
+ failureCallback.onFailure(operation, e);
+ }
}
} finally {
operation.onPostExecute(success);
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index a6c3dd1071c9..1f8a829aaacd 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -32,6 +32,8 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.view.Window.DECOR_CAPTION_SHADE_DARK;
import static android.view.Window.DECOR_CAPTION_SHADE_LIGHT;
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
@@ -97,6 +99,7 @@ import android.view.Window;
import android.view.WindowCallbacks;
import android.view.WindowInsets;
import android.view.WindowInsetsController;
+import android.view.WindowInsetsController.Appearance;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
@@ -137,19 +140,16 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
private static final int SCRIM_LIGHT = 0xe6ffffff; // 90% white
public static final ColorViewAttributes STATUS_BAR_COLOR_VIEW_ATTRIBUTES =
- new ColorViewAttributes(SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS,
+ new ColorViewAttributes(FLAG_TRANSLUCENT_STATUS,
Gravity.TOP, Gravity.LEFT, Gravity.RIGHT,
Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME,
- com.android.internal.R.id.statusBarBackground,
- FLAG_FULLSCREEN, ITYPE_STATUS_BAR);
+ com.android.internal.R.id.statusBarBackground, ITYPE_STATUS_BAR);
public static final ColorViewAttributes NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES =
- new ColorViewAttributes(
- SYSTEM_UI_FLAG_HIDE_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION,
+ new ColorViewAttributes(FLAG_TRANSLUCENT_NAVIGATION,
Gravity.BOTTOM, Gravity.RIGHT, Gravity.LEFT,
Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME,
- com.android.internal.R.id.navigationBarBackground,
- 0 /* hideWindowFlag */, ITYPE_NAVIGATION_BAR);
+ com.android.internal.R.id.navigationBarBackground, ITYPE_NAVIGATION_BAR);
// This is used to workaround an issue where the PiP shadow can be transparent if the window
// background is transparent
@@ -1016,6 +1016,11 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
}
@Override
+ public void onSystemBarAppearanceChanged(@WindowInsetsController.Appearance int appearance) {
+ updateColorViews(null /* insets */, true /* animate */);
+ }
+
+ @Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
final WindowManager.LayoutParams attrs = mWindow.getAttributes();
mFloatingInsets.setEmpty();
@@ -1094,6 +1099,11 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
& FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
mLastWindowFlags = attrs.flags;
+ final ViewRootImpl viewRoot = getViewRootImpl();
+ final @Appearance int appearance = viewRoot != null
+ ? viewRoot.mWindowAttributes.insetsFlags.appearance
+ : controller.getSystemBarsAppearance();
+
if (insets != null) {
final Insets systemBarInsets = insets.getInsets(WindowInsets.Type.systemBars());
final Insets stableBarInsets = insets.getInsetsIgnoringVisibility(
@@ -1130,8 +1140,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
boolean navBarToRightEdge = isNavBarToRightEdge(mLastBottomInset, mLastRightInset);
boolean navBarToLeftEdge = isNavBarToLeftEdge(mLastBottomInset, mLastLeftInset);
int navBarSize = getNavBarSize(mLastBottomInset, mLastRightInset, mLastLeftInset);
- updateColorViewInt(mNavigationColorViewState, sysUiVisibility,
- calculateNavigationBarColor(), mWindow.mNavigationBarDividerColor, navBarSize,
+ updateColorViewInt(mNavigationColorViewState, calculateNavigationBarColor(appearance),
+ mWindow.mNavigationBarDividerColor, navBarSize,
navBarToRightEdge || navBarToLeftEdge, navBarToLeftEdge,
0 /* sideInset */, animate && !disallowAnimate,
mForceWindowDrawsBarBackgrounds, controller);
@@ -1139,9 +1149,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
mDrawLegacyNavigationBarBackground = mNavigationColorViewState.visible
&& (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0;
if (oldDrawLegacy != mDrawLegacyNavigationBarBackground) {
- ViewRootImpl vri = getViewRootImpl();
- if (vri != null) {
- vri.requestInvalidateRootRenderNode();
+ if (viewRoot != null) {
+ viewRoot.requestInvalidateRootRenderNode();
}
}
@@ -1151,15 +1160,14 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
&& mNavigationColorViewState.present;
int statusBarSideInset = statusBarNeedsRightInset ? mLastRightInset
: statusBarNeedsLeftInset ? mLastLeftInset : 0;
- updateColorViewInt(mStatusColorViewState, sysUiVisibility,
- calculateStatusBarColor(), 0, mLastTopInset,
- false /* matchVertical */, statusBarNeedsLeftInset, statusBarSideInset,
- animate && !disallowAnimate,
+ int statusBarColor = calculateStatusBarColor(appearance);
+ updateColorViewInt(mStatusColorViewState, statusBarColor, 0,
+ mLastTopInset, false /* matchVertical */, statusBarNeedsLeftInset,
+ statusBarSideInset, animate && !disallowAnimate,
mForceWindowDrawsBarBackgrounds, controller);
if (mHasCaption) {
- final int captionColor = calculateStatusBarColor();
- mDecorCaptionView.getCaption().setBackgroundColor(captionColor);
+ mDecorCaptionView.getCaption().setBackgroundColor(statusBarColor);
updateDecorCaptionShade();
}
}
@@ -1286,29 +1294,30 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
return mOriginalBackgroundDrawable;
}
- private int calculateStatusBarColor() {
+ private int calculateStatusBarColor(@Appearance int appearance) {
return calculateBarColor(mWindow.getAttributes().flags, FLAG_TRANSLUCENT_STATUS,
mSemiTransparentBarColor, mWindow.mStatusBarColor,
- getWindowSystemUiVisibility(), SYSTEM_UI_FLAG_LIGHT_STATUS_BAR,
+ appearance, APPEARANCE_LIGHT_STATUS_BARS,
mWindow.mEnsureStatusBarContrastWhenTransparent);
}
- private int calculateNavigationBarColor() {
+ private int calculateNavigationBarColor(@Appearance int appearance) {
return calculateBarColor(mWindow.getAttributes().flags, FLAG_TRANSLUCENT_NAVIGATION,
mSemiTransparentBarColor, mWindow.mNavigationBarColor,
- getWindowSystemUiVisibility(), SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
+ appearance, APPEARANCE_LIGHT_NAVIGATION_BARS,
mWindow.mEnsureNavigationBarContrastWhenTransparent
&& getContext().getResources().getBoolean(R.bool.config_navBarNeedsScrim));
}
public static int calculateBarColor(int flags, int translucentFlag, int semiTransparentBarColor,
- int barColor, int sysuiVis, int lightSysuiFlag, boolean scrimTransparent) {
+ int barColor, @Appearance int appearance, @Appearance int lightAppearanceFlag,
+ boolean scrimTransparent) {
if ((flags & translucentFlag) != 0) {
return semiTransparentBarColor;
} else if ((flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0) {
return Color.BLACK;
} else if (scrimTransparent && Color.alpha(barColor) == 0) {
- boolean light = (sysuiVis & lightSysuiFlag) != 0;
+ boolean light = (appearance & lightAppearanceFlag) != 0;
return light ? SCRIM_LIGHT : semiTransparentBarColor;
} else {
return barColor;
@@ -1327,7 +1336,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
* Update a color view
*
* @param state the color view to update.
- * @param sysUiVis the current systemUiVisibility to apply.
* @param color the current color to apply.
* @param dividerColor the current divider color to apply.
* @param size the current size in the non-parent-matching dimension.
@@ -1336,12 +1344,10 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
* @param sideMargin sideMargin for the color view.
* @param animate if true, the change will be animated.
*/
- private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color,
- int dividerColor, int size, boolean verticalBar, boolean seascape, int sideMargin,
- boolean animate, boolean force, WindowInsetsController controller) {
- state.present = ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL
- ? state.attributes.isPresent(sysUiVis, mWindow.getAttributes().flags, force)
- : state.attributes.isPresent(
+ private void updateColorViewInt(final ColorViewState state, int color, int dividerColor,
+ int size, boolean verticalBar, boolean seascape, int sideMargin, boolean animate,
+ boolean force, WindowInsetsController controller) {
+ state.present = state.attributes.isPresent(
controller.isRequestedVisible(state.attributes.insetsType),
mWindow.getAttributes().flags, force);
boolean show = state.attributes.isVisible(state.present, color,
@@ -2583,37 +2589,25 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
public static class ColorViewAttributes {
final int id;
- final int systemUiHideFlag;
final int translucentFlag;
final int verticalGravity;
final int horizontalGravity;
final int seascapeGravity;
final String transitionName;
- final int hideWindowFlag;
final @InternalInsetsType int insetsType;
- private ColorViewAttributes(int systemUiHideFlag, int translucentFlag, int verticalGravity,
- int horizontalGravity, int seascapeGravity, String transitionName, int id,
- int hideWindowFlag, @InternalInsetsType int insetsType) {
+ private ColorViewAttributes(int translucentFlag, int verticalGravity, int horizontalGravity,
+ int seascapeGravity, String transitionName, int id,
+ @InternalInsetsType int insetsType) {
this.id = id;
- this.systemUiHideFlag = systemUiHideFlag;
this.translucentFlag = translucentFlag;
this.verticalGravity = verticalGravity;
this.horizontalGravity = horizontalGravity;
this.seascapeGravity = seascapeGravity;
this.transitionName = transitionName;
- this.hideWindowFlag = hideWindowFlag;
this.insetsType = insetsType;
}
- // TODO(b/118118435): remove after migration
- public boolean isPresent(int sysUiVis, int windowFlags, boolean force) {
- return (sysUiVis & systemUiHideFlag) == 0
- && (windowFlags & hideWindowFlag) == 0
- && ((windowFlags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
- || force);
- }
-
public boolean isPresent(boolean requestedVisible, int windowFlags, boolean force) {
return requestedVisible
&& ((windowFlags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 || force);
@@ -2625,12 +2619,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
&& ((windowFlags & translucentFlag) == 0 || force);
}
- // TODO(b/118118435): remove after migration
- public boolean isVisible(int sysUiVis, int color, int windowFlags, boolean force) {
- final boolean present = isPresent(sysUiVis, windowFlags, force);
- return isVisible(present, color, windowFlags, force);
- }
-
public boolean isVisible(InsetsState state, int color, int windowFlags, boolean force) {
final boolean present = isPresent(state.getSource(insetsType).isVisible(), windowFlags,
force);
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 25d1ae699aa6..44dca9bae3da 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -63,7 +63,7 @@ oneway interface IPhoneStateListener {
void onCallAttributesChanged(in CallAttributes callAttributes);
void onEmergencyNumberListChanged(in Map emergencyNumberList);
void onOutgoingEmergencyCall(in EmergencyNumber placedEmergencyNumber, int subscriptionId);
- void onOutgoingEmergencySms(in EmergencyNumber sentEmergencyNumber);
+ void onOutgoingEmergencySms(in EmergencyNumber sentEmergencyNumber, int subscriptionId);
void onCallDisconnectCauseChanged(in int disconnectCause, in int preciseDisconnectCause);
void onImsCallDisconnectCauseChanged(in ImsReasonInfo imsReasonInfo);
void onRegistrationFailed(in CellIdentity cellIdentity,
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index 50a557bb53a1..3acd15a8f340 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -59,7 +59,8 @@ private:
jobject mReceiverWeakGlobal;
sp<MessageQueue> mMessageQueue;
- void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count) override;
+ void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count,
+ int64_t sharedTimelineFrameCount) 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;
@@ -90,14 +91,14 @@ void NativeDisplayEventReceiver::dispose() {
}
void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId,
- uint32_t count) {
+ uint32_t count, int64_t frameTimelineVsyncId) {
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);
+ timestamp, displayId.value, count, frameTimelineVsyncId);
ALOGV("receiver %p ~ Returned from vsync handler.", this);
}
@@ -196,8 +197,8 @@ int register_android_view_DisplayEventReceiver(JNIEnv* env) {
jclass clazz = FindClassOrDie(env, "android/view/DisplayEventReceiver");
gDisplayEventReceiverClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
- gDisplayEventReceiverClassInfo.dispatchVsync = GetMethodIDOrDie(env,
- gDisplayEventReceiverClassInfo.clazz, "dispatchVsync", "(JJI)V");
+ gDisplayEventReceiverClassInfo.dispatchVsync =
+ GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchVsync", "(JJIJ)V");
gDisplayEventReceiverClassInfo.dispatchHotplug = GetMethodIDOrDie(env,
gDisplayEventReceiverClassInfo.clazz, "dispatchHotplug", "(JJZ)V");
gDisplayEventReceiverClassInfo.dispatchConfigChanged = GetMethodIDOrDie(env,
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/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 3ec14c01c363..50912702e3d6 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;
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 7f743ef037a2..5f590be72f52 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -518,11 +518,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 ];
diff --git a/core/proto/android/stats/hdmi/enums.proto b/core/proto/android/stats/hdmi/enums.proto
new file mode 100644
index 000000000000..acb8899fbdd9
--- /dev/null
+++ b/core/proto/android/stats/hdmi/enums.proto
@@ -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.
+ */
+
+syntax = "proto2";
+
+package android.stats.hdmi;
+option java_multiple_files = true;
+option java_outer_classname = "HdmiStatsEnums";
+
+// HDMI CEC logical addresses.
+// Values correspond to "CEC Table 5 Logical Addresses" in the HDMI CEC 1.4b spec.
+enum LogicalAddress {
+ LOGICAL_ADDRESS_UNKNOWN = -1;
+ TV = 0;
+ RECORDING_DEVICE_1 = 1;
+ RECORDING_DEVICE_2 = 2;
+ TUNER_1 = 3;
+ PLAYBACK_DEVICE_1 = 4;
+ AUDIO_SYSTEM = 5;
+ TUNER_2 = 6;
+ TUNER_3 = 7;
+ PLAYBACK_DEVICE_2 = 8;
+ RECORDING_DEVICE_3 = 9;
+ TUNER_4 = 10;
+ PLAYBACK_DEVICE_3 = 11;
+ RESERVED_1 = 12;
+ RESERVED_2 = 13;
+ SPECIFIC_USE = 14;
+ UNREGISTERED_OR_BROADCAST = 15;
+}
+
+// The relationship between two paths.
+// Values correspond exactly to PathRelationship in com.android.server.hdmi.Constants.
+enum PathRelationship {
+ RELATIONSHIP_TO_ACTIVE_SOURCE_UNKNOWN = 0;
+ DIFFERENT_BRANCH = 1;
+ ANCESTOR = 2;
+ DESCENDANT = 3;
+ SIBLING = 4;
+ SAME = 5;
+}
+
+// The result of attempting to send a HDMI CEC message.
+// Values correspond to the constants in android.hardware.tv.cec.V1_0.SendMessageResult,
+// offset by 10.
+enum SendMessageResult {
+ SEND_MESSAGE_RESULT_UNKNOWN = 0;
+ SUCCESS = 10;
+ NACK = 11;
+ BUSY = 12;
+ FAIL = 13;
+}
+
+// Whether a HDMI CEC message is sent from this device, to this device, or neither.
+enum MessageDirection {
+ MESSAGE_DIRECTION_UNKNOWN = 0;
+ MESSAGE_DIRECTION_OTHER = 1; // None of the other options.
+ OUTGOING = 2; // Sent from this device.
+ INCOMING = 3; // Sent to this device.
+ TO_SELF = 4; // Sent from this device, to this device. Indicates a bug.
+}
+
+// User control commands. Each value can represent an individual command, or a set of commands.
+// Values correspond to "CEC Table 30 UI Command Codes" in the HDMI CEC 1.4b spec, offset by 0x100.
+enum UserControlPressedCommand {
+ USER_CONTROL_PRESSED_COMMAND_UNKNOWN = 0;
+
+ // Represents all codes that are not represented by another value.
+ USER_CONTROL_PRESSED_COMMAND_OTHER = 1;
+
+ // Represents all number codes (codes 0x1E through 0x29).
+ NUMBER = 2;
+
+ // Navigation
+ SELECT = 0x100;
+ UP = 0x101;
+ DOWN = 0x102;
+ LEFT = 0x103;
+ RIGHT = 0x104;
+ RIGHT_UP = 0x105;
+ RIGHT_DOWN = 0x106;
+ LEFT_UP = 0x107;
+ LEFT_DOWN = 0x108;
+ EXIT = 0x10D;
+
+ // Volume
+ VOLUME_UP = 0x141;
+ VOLUME_DOWN = 0x142;
+ VOLUME_MUTE = 0x143;
+
+ // Power
+ POWER = 0x140;
+ POWER_TOGGLE = 0x16B;
+ POWER_OFF = 0x16C;
+ POWER_ON = 0x16D;
+}
+
+// Reason parameter of the <Feature Abort> message.
+// Values correspond to "CEC Table 29 Operand Descriptions" in the HDMI CEC 1.4b spec,
+// offset by 10.
+enum FeatureAbortReason {
+ FEATURE_ABORT_REASON_UNKNOWN = 0;
+ UNRECOGNIZED_OPCODE = 10;
+ NOT_IN_CORRECT_MODE_TO_RESPOND = 11;
+ CANNOT_PROVIDE_SOURCE = 12;
+ INVALID_OPERAND = 13;
+ REFUSED = 14;
+ UNABLE_TO_DETERMINE = 15;
+} \ No newline at end of file
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 68e2891657ac..34543f13c166 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -238,6 +238,8 @@
android:name="com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_DELIVERY" />
<protected-broadcast
android:name="android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED" />
+ <protected-broadcast
+ android:name="android.bluetooth.action.TETHERING_STATE_CHANGED" />
<protected-broadcast android:name="android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED" />
<protected-broadcast android:name="android.bluetooth.pbapclient.profile.action.CONNECTION_STATE_CHANGED" />
<protected-broadcast android:name="android.bluetooth.sap.profile.action.CONNECTION_STATE_CHANGED" />
@@ -2457,8 +2459,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"
@@ -3912,6 +3915,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 -->
@@ -3974,6 +3985,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 -->
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index b1183689c5ee..bea18616cba9 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -1219,7 +1219,7 @@
<string name="dump_heap_ready_text" msgid="5849618132123045516">"የ<xliff:g id="PROC">%1$s</xliff:g> ሂደት ተራጋፊ ክምር ለማጋራት ለእርስዎ ይገኛል። ይጠንቀቁ፦ ይህ ተራጋፊ ክምር ሂደቱ ሊደርስባቸው የሚችለው ማንኛውም የግል መረጃ ሊኖረው ይችላል፣ ይህ እርስዎ የተየቧቸውን ነገሮች ሊያካትት ይችላል።"</string>
<string name="sendText" msgid="493003724401350724">"ለፅሁፍ ድርጊት ምረጥ"</string>
<string name="volume_ringtone" msgid="134784084629229029">"የስልክ ጥሪ ድምፅ"</string>
- <string name="volume_music" msgid="7727274216734955095">" ማህደረ መረጃ ክፍልፍል"</string>
+ <string name="volume_music" msgid="7727274216734955095">"የማህደረ መረጃ ድምጽ መጠን"</string>
<string name="volume_music_hint_playing_through_bluetooth" msgid="2614142915948898228">"በብሉቱዝ በኩል ማጫወት"</string>
<string name="volume_music_hint_silent_ringtone_selected" msgid="1514829655029062233">"የፀጥታ የስልክ የደውል ድምፅ ተዘጋጅቷል"</string>
<string name="volume_call" msgid="7625321655265747433">"የጥሪ ላይ ድም ፅ መጨመሪያ/መቀነሻ"</string>
@@ -1230,7 +1230,7 @@
<string name="volume_icon_description_bluetooth" msgid="7540388479345558400">"የብሉቱዝ ድምፅ መጠን"</string>
<string name="volume_icon_description_ringer" msgid="2187800636867423459">"የስልክ ጥሪ ድምፅ መጠን"</string>
<string name="volume_icon_description_incall" msgid="4491255105381227919">"የስልክ ጥሪ ድምፅ መጠን"</string>
- <string name="volume_icon_description_media" msgid="4997633254078171233">"የማህደረ መረጃ ክፍልፍል"</string>
+ <string name="volume_icon_description_media" msgid="4997633254078171233">"የማህደረ መረጃ ድምጽ መጠን"</string>
<string name="volume_icon_description_notification" msgid="579091344110747279">"የማሳወቂያ ክፍልፍል"</string>
<string name="ringtone_default" msgid="9118299121288174597">"ነባሪ የስልክ ላይ ጥሪ"</string>
<string name="ringtone_default_with_actual" msgid="2709686194556159773">"ነባሪ (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index f07fea7bcbbe..68323448a44f 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -1301,7 +1301,7 @@
<string name="volume_ringtone" msgid="134784084629229029">"مستوى صوت الرنين"</string>
<string name="volume_music" msgid="7727274216734955095">"مستوى صوت الوسائط"</string>
<string name="volume_music_hint_playing_through_bluetooth" msgid="2614142915948898228">"تشغيل من خلال البلوتوث"</string>
- <string name="volume_music_hint_silent_ringtone_selected" msgid="1514829655029062233">"تم تعيين نغمة الرنين الصامتة"</string>
+ <string name="volume_music_hint_silent_ringtone_selected" msgid="1514829655029062233">"تم ضبط نغمة الرنين الصامتة"</string>
<string name="volume_call" msgid="7625321655265747433">"مستوى صوت المكالمات الواردة"</string>
<string name="volume_bluetooth_call" msgid="2930204618610115061">"مستوى صوت المكالمة الواردة بالبلوتوث"</string>
<string name="volume_alarm" msgid="4486241060751798448">"مستوى صوت المنبّه"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 56736c0d6780..08aeacf9307e 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -720,10 +720,10 @@
<item msgid="6216981255272016212">"Асаблівы"</item>
</string-array>
<string-array name="emailAddressTypes">
- <item msgid="7786349763648997741">"Хатні"</item>
- <item msgid="435564470865989199">"Працоўны"</item>
- <item msgid="4199433197875490373">"Іншы"</item>
- <item msgid="3233938986670468328">"Карыстальніцкі"</item>
+ <item msgid="7786349763648997741">"Асабістая"</item>
+ <item msgid="435564470865989199">"Працоўная"</item>
+ <item msgid="4199433197875490373">"Іншая"</item>
+ <item msgid="3233938986670468328">"Карыстальніцкая"</item>
</string-array>
<string-array name="postalAddressTypes">
<item msgid="3861463339764243038">"На Галоўную старонку"</item>
@@ -773,14 +773,14 @@
<string name="phoneTypeWorkPager" msgid="3748332310638505234">"Працоўны пэйджар"</string>
<string name="phoneTypeAssistant" msgid="757550783842231039">"Асістэнт"</string>
<string name="phoneTypeMms" msgid="1799747455131365989">"MMS"</string>
- <string name="eventTypeCustom" msgid="3257367158986466481">"Карыстальніцкі"</string>
+ <string name="eventTypeCustom" msgid="3257367158986466481">"Карыстальніцкае"</string>
<string name="eventTypeBirthday" msgid="7770026752793912283">"Дзень нараджэння"</string>
<string name="eventTypeAnniversary" msgid="4684702412407916888">"Гадавіна"</string>
<string name="eventTypeOther" msgid="530671238533887997">"Іншае"</string>
- <string name="emailTypeCustom" msgid="1809435350482181786">"Карыстальніцкі"</string>
+ <string name="emailTypeCustom" msgid="1809435350482181786">"Карыстальніцкая"</string>
<string name="emailTypeHome" msgid="1597116303154775999">"Хатні"</string>
- <string name="emailTypeWork" msgid="2020095414401882111">"Працоўны"</string>
- <string name="emailTypeOther" msgid="5131130857030897465">"Іншы"</string>
+ <string name="emailTypeWork" msgid="2020095414401882111">"Працоўная"</string>
+ <string name="emailTypeOther" msgid="5131130857030897465">"Іншая"</string>
<string name="emailTypeMobile" msgid="787155077375364230">"Мабільны"</string>
<string name="postalTypeCustom" msgid="5645590470242939129">"Карыстальніцкі"</string>
<string name="postalTypeHome" msgid="7562272480949727912">"Хатні"</string>
@@ -803,19 +803,19 @@
<string name="orgTypeWork" msgid="8684458700669564172">"Працоўная"</string>
<string name="orgTypeOther" msgid="5450675258408005553">"Іншая"</string>
<string name="orgTypeCustom" msgid="1126322047677329218">"Карыстальніцкі"</string>
- <string name="relationTypeCustom" msgid="282938315217441351">"Карыстальніцкі"</string>
+ <string name="relationTypeCustom" msgid="282938315217441351">"Карыстальніцкае"</string>
<string name="relationTypeAssistant" msgid="4057605157116589315">"Памочнік"</string>
<string name="relationTypeBrother" msgid="7141662427379247820">"Брат"</string>
<string name="relationTypeChild" msgid="9076258911292693601">"Дзіця"</string>
- <string name="relationTypeDomesticPartner" msgid="7825306887697559238">"Унутраны Партнёр"</string>
+ <string name="relationTypeDomesticPartner" msgid="7825306887697559238">"Сужыцель/сужыцелька"</string>
<string name="relationTypeFather" msgid="3856225062864790596">"Бацька"</string>
<string name="relationTypeFriend" msgid="3192092625893980574">"Сябар/сяброўка"</string>
<string name="relationTypeManager" msgid="2272860813153171857">"Кіраўнік"</string>
<string name="relationTypeMother" msgid="2331762740982699460">"Маці"</string>
- <string name="relationTypeParent" msgid="4177920938333039882">"Бацька"</string>
+ <string name="relationTypeParent" msgid="4177920938333039882">"Бацька/маці"</string>
<string name="relationTypePartner" msgid="4018017075116766194">"Партнёр"</string>
- <string name="relationTypeReferredBy" msgid="5285082289602849400">"Запрошаны"</string>
- <string name="relationTypeRelative" msgid="3396498519818009134">"Адносны"</string>
+ <string name="relationTypeReferredBy" msgid="5285082289602849400">"Рэкамендацыя"</string>
+ <string name="relationTypeRelative" msgid="3396498519818009134">"Радня"</string>
<string name="relationTypeSister" msgid="3721676005094140671">"Сястра"</string>
<string name="relationTypeSpouse" msgid="6916682664436031703">"Муж/жонка"</string>
<string name="sipAddressTypeCustom" msgid="6283889809842649336">"Карыстальніцкі"</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 5ffc420b13b4..f11a228ecae8 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -1138,7 +1138,7 @@
<string name="whichGiveAccessToApplicationLabel" msgid="7805857277166106236">"ઍક્સેસ આપો"</string>
<string name="whichEditApplication" msgid="6191568491456092812">"આનાથી સંપાદિત કરો"</string>
<string name="whichEditApplicationNamed" msgid="8096494987978521514">"%1$s સાથે સંપાદિત કરો"</string>
- <string name="whichEditApplicationLabel" msgid="1463288652070140285">"સંપાદિત કરો"</string>
+ <string name="whichEditApplicationLabel" msgid="1463288652070140285">"ફેરફાર કરો"</string>
<string name="whichSendApplication" msgid="4143847974460792029">"શેર કરો"</string>
<string name="whichSendApplicationNamed" msgid="4470386782693183461">"%1$s સાથે શેર કરો"</string>
<string name="whichSendApplicationLabel" msgid="7467813004769188515">"શેર કરો"</string>
@@ -1224,7 +1224,7 @@
<string name="volume_music_hint_silent_ringtone_selected" msgid="1514829655029062233">"સાઇલેન્ટ રિંગટોન સેટ કરી"</string>
<string name="volume_call" msgid="7625321655265747433">"ઇન-કૉલ વૉલ્યૂમ"</string>
<string name="volume_bluetooth_call" msgid="2930204618610115061">"બ્લૂટૂથ ઇન-કૉલ વૉલ્યૂમ"</string>
- <string name="volume_alarm" msgid="4486241060751798448">"એલાર્મ વૉલ્યૂમ"</string>
+ <string name="volume_alarm" msgid="4486241060751798448">"અલાર્મ વૉલ્યૂમ"</string>
<string name="volume_notification" msgid="6864412249031660057">"સૂચના વૉલ્યૂમ"</string>
<string name="volume_unknown" msgid="4041914008166576293">"વૉલ્યૂમ"</string>
<string name="volume_icon_description_bluetooth" msgid="7540388479345558400">"બ્લૂટૂથ વૉલ્યૂમ"</string>
@@ -1513,7 +1513,7 @@
<string name="storage_usb_drive" msgid="448030813201444573">"USB ડ્રાઇવ"</string>
<string name="storage_usb_drive_label" msgid="6631740655876540521">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB ડ્રાઇવ"</string>
<string name="storage_usb" msgid="2391213347883616886">"USB સ્ટોરેજ"</string>
- <string name="extract_edit_menu_button" msgid="63954536535863040">"સંપાદિત કરો"</string>
+ <string name="extract_edit_menu_button" msgid="63954536535863040">"ફેરફાર કરો"</string>
<string name="data_usage_warning_title" msgid="9034893717078325845">"ડેટા ચેતવણી"</string>
<string name="data_usage_warning_body" msgid="1669325367188029454">"તમે <xliff:g id="APP">%s</xliff:g> ડેટા વાપર્યો છે"</string>
<string name="data_usage_mobile_limit_title" msgid="3911447354393775241">"મોબાઇલ ડેટાની મર્યાદા આવી ગઈ"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index f4fb83deba39..f6f50402b3ff 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -1234,7 +1234,7 @@
<string name="volume_icon_description_notification" msgid="579091344110747279">"Volume pemberitahuan"</string>
<string name="ringtone_default" msgid="9118299121288174597">"Nada dering default"</string>
<string name="ringtone_default_with_actual" msgid="2709686194556159773">"Default (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
- <string name="ringtone_silent" msgid="397111123930141876">"Tidak Ada"</string>
+ <string name="ringtone_silent" msgid="397111123930141876">"Tidak ada"</string>
<string name="ringtone_picker_title" msgid="667342618626068253">"Nada dering"</string>
<string name="ringtone_picker_title_alarm" msgid="7438934548339024767">"Suara alarm"</string>
<string name="ringtone_picker_title_notification" msgid="6387191794719608122">"Suara notifikasi"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 6d063b647c41..9798cc9db1b8 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -1792,7 +1792,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">"Батарея жұмысының ұзақтығын арттыру үшін Battery Saver:\n\n•қараңғы тақырыпты іске қосады;\n•фондық әрекеттерді, кейбір көрнекі әсерлерді және \"Ok Google\" сияқты басқа да функцияларды өшіреді не шектейді.\n\n"<annotation id="url">"Толығырақ"</annotation></string>
+ <string name="battery_saver_description_with_learn_more" msgid="4424488535318105801">"Батарея жұмысының ұзақтығын арттыру үшін Батареяны үнемдеу режимі:\n\n•қараңғы тақырыпты іске қосады;\n•фондық әрекеттерді, кейбір көрнекі әсерлерді және \"Ok Google\" сияқты басқа да функцияларды өшіреді не шектейді.\n\n"<annotation id="url">"Толығырақ"</annotation></string>
<string name="battery_saver_description" msgid="6794188153647295212">"Батарея ұзағырақ жұмыс істеуі үшін, Battery Saver:\n\n• қараңғы тақырыпты қосады;\n•фондық жұмысты, кейбір визуалды әсерлерді және \"Ok Google\" сияқты басқа функцияларды өшіреді не шектейді."</string>
<string name="data_saver_description" msgid="4995164271550590517">"Дерек шығынын азайту үшін Data Saver функциясы кейбір қолданбаларға деректерді фондық режимде жіберуге және алуға жол бермейді. Ашық тұрған қолданба деректерді пайдаланады, бірақ шектеулі шамада (мысалы, кескіндер оларды түрткенге дейін көрсетілмейді)."</string>
<string name="data_saver_enable_title" msgid="7080620065745260137">"Data Saver функциясын қосу керек пе?"</string>
@@ -1912,7 +1912,7 @@
<string name="conference_call" msgid="5731633152336490471">"Конференциялық қоңырау"</string>
<string name="tooltip_popup_title" msgid="7863719020269945722">"Қалқыма сөзкөмек"</string>
<string name="app_category_game" msgid="4534216074910244790">"Ойындар"</string>
- <string name="app_category_audio" msgid="8296029904794676222">"Музыка және аудиомазмұн"</string>
+ <string name="app_category_audio" msgid="8296029904794676222">"Музыка және аудио"</string>
<string name="app_category_video" msgid="2590183854839565814">"Фильм және бейне"</string>
<string name="app_category_image" msgid="7307840291864213007">"Суреттер және кескіндер"</string>
<string name="app_category_social" msgid="2278269325488344054">"Әлеуметтік қолданба мен байланыс"</string>
@@ -2001,7 +2001,7 @@
<string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Режим туралы хабарландыру"</string>
<string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Батарея заряды азаюы мүмкін"</string>
<string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Батарея ұзаққа жетуі үшін, Battery Saver іске қосылды"</string>
- <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Battery Saver"</string>
+ <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Батареяны үнемдеу режимі"</string>
<string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Battery Saver өшірілді"</string>
<string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Телефонның заряды жеткілікті. Функцияларға енді шектеу қойылмайды."</string>
<string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"Планшеттің заряды жеткілікті. Функцияларға енді шектеу қойылмайды."</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index b9378def3491..a0b8faa12f78 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -2001,7 +2001,7 @@
<string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"ការ​ជូនដំណឹង​ព័ត៌មាន​របស់​មុខងារ​ទម្លាប់"</string>
<string name="dynamic_mode_notification_title" msgid="9205715501274608016">"ថ្ម​អាច​នឹង​អស់ មុនពេល​សាកថ្មធម្មតា"</string>
<string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"បាន​បើក​ដំណើរការកម្មវិធី​សន្សំ​ថ្ម ដើម្បីបង្កើនកម្រិត​ថាមពល​​ថ្ម"</string>
- <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"កម្មវិធីសន្សំថ្ម"</string>
+ <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"មុខងារ​សន្សំ​ថ្ម"</string>
<string name="battery_saver_off_notification_title" msgid="7637255960468032515">"កម្មវិធី​សន្សំ​ថ្ម​ត្រូវបានបិទ"</string>
<string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"ទូរសព្ទ​មាន​កម្រិតថ្ម​គ្រប់គ្រាន់​។ មុខងារ​ផ្សេងៗ​មិន​ត្រូវបាន​រឹតបន្តឹងទៀត​ទេ។"</string>
<string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"ថេប្លេត​មាន​កម្រិតថ្ម​គ្រប់គ្រាន់​។ មុខងារ​ផ្សេងៗ​មិន​ត្រូវបាន​រឹតបន្តឹងទៀត​ទេ។"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 29666a9aa5bd..14cfee028fdc 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -934,9 +934,9 @@
<string name="js_dialog_before_unload" msgid="7213364985774778744">"<xliff:g id="MESSAGE">%s</xliff:g>\n\nWeet u zeker dat u deze pagina wilt verlaten?"</string>
<string name="save_password_label" msgid="9161712335355510035">"Bevestigen"</string>
<string name="double_tap_toast" msgid="7065519579174882778">"Tip: dubbeltik om in en uit te zoomen."</string>
- <string name="autofill_this_form" msgid="3187132440451621492">"Autom. aanvullen"</string>
- <string name="setup_autofill" msgid="5431369130866618567">"Autom. aanvullen instellen"</string>
- <string name="autofill_window_title" msgid="4379134104008111961">"Automatisch aanvullen met <xliff:g id="SERVICENAME">%1$s</xliff:g>"</string>
+ <string name="autofill_this_form" msgid="3187132440451621492">"Autom. invullen"</string>
+ <string name="setup_autofill" msgid="5431369130866618567">"Autom. invullen instellen"</string>
+ <string name="autofill_window_title" msgid="4379134104008111961">"Automatisch invullen met <xliff:g id="SERVICENAME">%1$s</xliff:g>"</string>
<string name="autofill_address_name_separator" msgid="8190155636149596125">" "</string>
<string name="autofill_address_summary_name_format" msgid="3402882515222673691">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="760522655085707045">", "</string>
@@ -1104,7 +1104,7 @@
<string name="selectTextMode" msgid="3225108910999318778">"Tekst selecteren"</string>
<string name="undo" msgid="3175318090002654673">"Ongedaan maken"</string>
<string name="redo" msgid="7231448494008532233">"Opnieuw"</string>
- <string name="autofill" msgid="511224882647795296">"Automatisch aanvullen"</string>
+ <string name="autofill" msgid="511224882647795296">"Automatisch invullen"</string>
<string name="textSelectionCABTitle" msgid="5151441579532476940">"Tekstselectie"</string>
<string name="addToDictionary" msgid="8041821113480950096">"Toevoegen aan woordenboek"</string>
<string name="deleteText" msgid="4200807474529938112">"Verwijderen"</string>
@@ -1928,13 +1928,13 @@
<string name="time_picker_prompt_label" msgid="303588544656363889">"Typ een tijd"</string>
<string name="time_picker_text_input_mode_description" msgid="4761160667516611576">"Schakel naar de tekstinvoermodus om de tijd in te voeren."</string>
<string name="time_picker_radial_mode_description" msgid="1222342577115016953">"Schakel naar de klokmodus om de tijd in te voeren."</string>
- <string name="autofill_picker_accessibility_title" msgid="4425806874792196599">"Opties voor automatisch aanvullen"</string>
- <string name="autofill_save_accessibility_title" msgid="1523225776218450005">"Opslaan voor Automatisch aanvullen"</string>
+ <string name="autofill_picker_accessibility_title" msgid="4425806874792196599">"Opties voor automatisch invullen"</string>
+ <string name="autofill_save_accessibility_title" msgid="1523225776218450005">"Opslaan voor Automatisch invullen"</string>
<string name="autofill_error_cannot_autofill" msgid="6528827648643138596">"Content kan niet automatisch worden aangevuld"</string>
- <string name="autofill_picker_no_suggestions" msgid="1076022650427481509">"Geen suggesties van Automatisch aanvullen"</string>
+ <string name="autofill_picker_no_suggestions" msgid="1076022650427481509">"Geen suggesties van Automatisch invullen"</string>
<plurals name="autofill_picker_some_suggestions" formatted="false" msgid="6651883186966959978">
- <item quantity="other"><xliff:g id="COUNT">%1$s</xliff:g> suggesties van Automatisch aanvullen</item>
- <item quantity="one">Eén suggestie van Automatisch aanvullen</item>
+ <item quantity="other"><xliff:g id="COUNT">%1$s</xliff:g> suggesties van Automatisch invullen</item>
+ <item quantity="one">Eén suggestie van Automatisch invullen</item>
</plurals>
<string name="autofill_save_title" msgid="7719802414283739775">"Opslaan in "<b>"<xliff:g id="LABEL">%1$s</xliff:g>"</b>"?"</string>
<string name="autofill_save_title_with_type" msgid="3002460014579799605">"<xliff:g id="TYPE">%1$s</xliff:g> opslaan in "<b>"<xliff:g id="LABEL">%2$s</xliff:g>"</b>"?"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 903ff83a0572..b5560f186e3a 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -899,7 +899,7 @@
<string name="keyguard_accessibility_unlock_area_collapsed" msgid="4729922043778400434">"ਅਣਲਾਕ ਖੇਤਰ ਨਸ਼ਟ ਕੀਤਾ।"</string>
<string name="keyguard_accessibility_widget" msgid="6776892679715699875">"<xliff:g id="WIDGET_INDEX">%1$s</xliff:g> ਵਿਜੇਟ।"</string>
<string name="keyguard_accessibility_user_selector" msgid="1466067610235696600">"ਉਪਭੋਗਤਾ ਚੋਣਕਾਰ"</string>
- <string name="keyguard_accessibility_status" msgid="6792745049712397237">"ਅਵਸਥਾ"</string>
+ <string name="keyguard_accessibility_status" msgid="6792745049712397237">"ਸਥਿਤੀ"</string>
<string name="keyguard_accessibility_camera" msgid="7862557559464986528">"ਕੈਮਰਾ"</string>
<string name="keygaurd_accessibility_media_controls" msgid="2267379779900620614">"ਮੀਡੀਆ ਨਿਯੰਤਰਣ"</string>
<string name="keyguard_accessibility_widget_reorder_start" msgid="7066213328912939191">"ਵਿਜੇਟ ਨੂੰ ਪੁਨਰ ਤਰਤੀਬ ਦੇਣਾ ਸ਼ੁਰੂ ਹੋਇਆ।"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 4f196440334c..7671809ebcc8 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -1793,7 +1793,7 @@
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Administrator tomonidan o‘chirilgan"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
<string name="battery_saver_description_with_learn_more" msgid="4424488535318105801">"Batareya quvvatini uzaytirish uchun Quvvat tejash funksiyasi:\n\n• Tungi mavzuni yoqadi\n• Fondagi harakatlar, vizual effektlar va “Hey Google” kabi boshqa funksiyalarni faolsizlantiradi\n\n"<annotation id="url">"Batafsil"</annotation></string>
- <string name="battery_saver_description" msgid="6794188153647295212">"Batareya quvvatini uzaytirish uchun Quvvat tejash funksiyasi:\n\n• Tungi mavzuni yoqadi\n• Fondagi harakatlar, vizual effektlar va “Hey Google” kabi boshqa funksiyalarni faolsizlantiradi"</string>
+ <string name="battery_saver_description" msgid="6794188153647295212">"Batareya quvvatini uzaytirish uchun Quvvat tejash funksiyasi:\n\n• Tungi mavzuni yoqadi\n• Fondagi harakatlar, vizual effektlar va “Ok Google” kabi boshqa funksiyalarni faolsizlantiradi"</string>
<string name="data_saver_description" msgid="4995164271550590517">"Trafik tejash rejimida ayrim ilovalar uchun orqa fonda internetdan foydalanish imkoniyati cheklanadi. Siz ishlatayotgan ilova zaruratga qarab internet-trafik sarflashi mumkin, biroq cheklangan miqdorda. Masalan, rasmlar ustiga bosmaguningizcha ular yuklanmaydi."</string>
<string name="data_saver_enable_title" msgid="7080620065745260137">"Trafik tejash yoqilsinmi?"</string>
<string name="data_saver_enable_button" msgid="4399405762586419726">"Yoqish"</string>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index fbbdaa3d7358..1c71baeaf46a 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -239,7 +239,9 @@
<!-- Old synonym for "privileged". Deprecated in API level 23. -->
<flag name="system" value="0x10" />
<!-- Additional flag from base permission type: this permission can also
- (optionally) be granted to development applications. -->
+ (optionally) be granted to development applications. Although undocumented, the
+ permission state used to be shared by all users (including future users), but it is
+ managed per-user since API level 31. -->
<flag name="development" value="0x20" />
<!-- Additional flag from base permission type: this permission is closely
associated with an app op for controlling access. -->
@@ -359,7 +361,9 @@
package manager. As such, its use is strongly discouraged and may be
removed in a future version of Android. Instead, apps should use proper
communication mechanisms, such as services and content providers,
- to facilitate interoperability between shared components. -->
+ to facilitate interoperability between shared components. Note that
+ existing apps cannot remove this value, as migrating off a
+ shared user ID is not supported. -->
<attr name="sharedUserId" format="string" />
<!-- Specify a label for the shared user UID of this package. This is
@@ -992,6 +996,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
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 592afa7012b5..89e348ab57b3 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3436,7 +3436,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-->
@@ -4418,4 +4418,7 @@
<!-- Component names of the services which will keep critical code path warm -->
<string-array name="config_keep_warming_services" translatable="false" />
+
+ <!-- WindowsManager JetPack display features -->
+ <string name="config_display_features" translatable="false" />
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index a5e5fba9a14e..03975daf1f76 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4888,6 +4888,9 @@
<item quantity="other">For %d hr</item>
</plurals>
+ <!-- Zen mode condition - line two: ending time indicating the next day. [CHAR LIMIT=NONE] -->
+ <string name="zen_mode_until_next_day">Until <xliff:g id="formattedTime" example="Tue, 10 PM">%1$s</xliff:g></string>
+
<!-- Zen mode condition - line two: ending time. [CHAR LIMIT=NONE] -->
<string name="zen_mode_until">Until <xliff:g id="formattedTime" example="10:00 PM">%1$s</xliff:g></string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index fdcd39a7f4cb..996e1f9fde9a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2393,6 +2393,7 @@
<java-symbol type="plurals" name="zen_mode_duration_hours_short" />
<java-symbol type="plurals" name="zen_mode_duration_minutes_summary_short" />
<java-symbol type="plurals" name="zen_mode_duration_hours_summary_short" />
+ <java-symbol type="string" name="zen_mode_until_next_day" />
<java-symbol type="string" name="zen_mode_until" />
<java-symbol type="string" name="zen_mode_feature_name" />
<java-symbol type="string" name="zen_mode_downtime_feature_name" />
@@ -4066,4 +4067,5 @@
<java-symbol type="dimen" name="config_defaultBinderHeavyHitterAutoSamplerThreshold" />
<java-symbol type="array" name="config_keep_warming_services" />
+ <java-symbol type="string" name="config_display_features" />
</resources>
diff --git a/core/tests/PackageInstallerSessions/Android.bp b/core/tests/PackageInstallerSessions/Android.bp
index e74f30ee10a4..055249d47cb3 100644
--- a/core/tests/PackageInstallerSessions/Android.bp
+++ b/core/tests/PackageInstallerSessions/Android.bp
@@ -36,6 +36,11 @@ android_test {
"framework-res",
],
+ java_resources: [
+ // Borrow an arbitrary test app from another module
+ ":PackageManagerTestAppVersion1",
+ ],
+
platform_apis: true,
sdk_version: "core_platform",
test_suites: ["device-tests"],
diff --git a/core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt b/core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt
index 494c92a8aa3f..b41018902b8b 100644
--- a/core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt
+++ b/core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt
@@ -16,7 +16,12 @@
package android.content.pm
+import android.app.Instrumentation
+import android.app.PendingIntent
+import android.content.BroadcastReceiver
import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
import android.content.pm.PackageInstaller.SessionParams
import android.platform.test.annotations.Presubmit
import androidx.test.InstrumentationRegistry
@@ -27,6 +32,8 @@ import org.junit.After
import org.junit.Before
import org.junit.Test
import org.testng.Assert.assertThrows
+import java.util.concurrent.ArrayBlockingQueue
+import java.util.concurrent.TimeUnit
import kotlin.random.Random
/**
@@ -53,15 +60,49 @@ class PackageSessionTests {
"android.permission.WRITE_CALL_LOG",
"android.permission.PROCESS_OUTGOING_CALLS"
)
+
+ private const val TEST_PKG_NAME = "com.android.server.pm.test.test_app"
+ private const val INTENT_ACTION = "com.android.server.pm.test.test_app.action"
}
private val context: Context = InstrumentationRegistry.getContext()
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val installer = context.packageManager.packageInstaller
+ private val receiver = object : BroadcastReceiver() {
+ private val results = ArrayBlockingQueue<Intent>(1)
+
+ override fun onReceive(context: Context, intent: Intent) {
+ results.add(intent)
+ }
+
+ fun makeIntentSender(sessionId: Int) = PendingIntent.getBroadcast(context, sessionId,
+ Intent(INTENT_ACTION), PendingIntent.FLAG_UPDATE_CURRENT).intentSender
+
+ fun getResult(unit: TimeUnit, timeout: Long) = results.poll(timeout, unit)
+
+ fun clear() = results.clear()
+ }
+
+ @Before
+ fun registerReceiver() {
+ receiver.clear()
+ context.registerReceiver(receiver, IntentFilter(INTENT_ACTION))
+ }
+
+ @After
+ fun unregisterReceiver() {
+ context.unregisterReceiver(receiver)
+ }
+
@Before
@After
fun abandonAllSessions() {
+ instrumentation.uiAutomation
+ .executeShellCommand("pm uninstall com.android.server.pm.test.test_app")
+ .close()
+
installer.mySessions.asSequence()
.map { it.sessionId }
.forEach {
@@ -82,7 +123,7 @@ class PackageSessionTests {
setAppLabel(longLabel)
}
- createSession(params) {
+ createAndAbandonSession(params) {
assertThat(installer.getSessionInfo(it)?.appLabel)
.isEqualTo(longLabel.take(PackageItemInfo.MAX_SAFE_LABEL_LENGTH))
}
@@ -95,7 +136,7 @@ class PackageSessionTests {
setAppPackageName(longName)
}
- createSession(params) {
+ createAndAbandonSession(params) {
assertThat(installer.getSessionInfo(it)?.appPackageName)
.isEqualTo(null)
}
@@ -108,7 +149,7 @@ class PackageSessionTests {
setInstallerPackageName(longName)
}
- createSession(params) {
+ createAndAbandonSession(params) {
// If a custom installer name is dropped, it defaults to the caller
assertThat(installer.getSessionInfo(it)?.installerPackageName)
.isEqualTo(context.packageName)
@@ -121,12 +162,39 @@ class PackageSessionTests {
setWhitelistedRestrictedPermissions(invalidPermissions())
}
- createSession(params) {
+ createAndAbandonSession(params) {
assertThat(installer.getSessionInfo(it)?.whitelistedRestrictedPermissions!!)
.containsExactlyElementsIn(RESTRICTED_PERMISSIONS)
}
}
+ @Test
+ fun fillPackageNameWithParsedValue() {
+ val params = SessionParams(SessionParams.MODE_FULL_INSTALL)
+ val sessionId = installer.createSession(params)
+ val session = installer.openSession(sessionId)
+
+ javaClass.classLoader.getResourceAsStream("PackageManagerTestAppVersion1.apk")!!
+ .use { input ->
+ session.openWrite("base", 0, -1)
+ .use { output -> input.copyTo(output) }
+ }
+
+ // Test instrumentation doesn't have install permissions, so use shell
+ ShellIdentityUtils.invokeWithShellPermissions {
+ session.commit(receiver.makeIntentSender(sessionId))
+ }
+ session.close()
+
+ // The actual contents aren't verified as part of this test. Only care about it finishing.
+ val result = receiver.getResult(TimeUnit.SECONDS, 30)
+ assertThat(result).isNotNull()
+
+ val sessionInfo = installer.getSessionInfo(sessionId)
+ assertThat(sessionInfo).isNotNull()
+ assertThat(sessionInfo!!.getAppPackageName()).isEqualTo(TEST_PKG_NAME)
+ }
+
@LargeTest
@Test
fun allocateMaxSessionsWithPermission() {
@@ -177,7 +245,7 @@ class PackageSessionTests {
repeat(10) { add(invalidPackageName(300)) }
}
- private fun createSession(params: SessionParams, block: (Int) -> Unit = {}) {
+ private fun createAndAbandonSession(params: SessionParams, block: (Int) -> Unit = {}) {
val sessionId = installer.createSession(params)
try {
block(sessionId)
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 0a751dd7c66b..23534e2ebebc 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -25,14 +25,14 @@ 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.ActivityThread;
+import android.app.ActivityThread.ActivityClientRecord;
import android.app.IApplicationThread;
import android.app.PictureInPictureParams;
import android.app.ResourcesManager;
@@ -114,11 +114,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 +245,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 +273,7 @@ public class ActivityThreadTest {
config.seq = BASE_SEQ;
config.orientation = ORIENTATION_PORTRAIT;
- activityThread.handleActivityConfigurationChanged(activity.getActivityToken(),
+ activityThread.handleActivityConfigurationChanged(getActivityClientRecord(activity),
config, INVALID_DISPLAY);
});
@@ -335,7 +334,7 @@ public class ActivityThreadTest {
config.seq = BASE_SEQ;
config.orientation = ORIENTATION_PORTRAIT;
- activityThread.handleActivityConfigurationChanged(activity.getActivityToken(),
+ activityThread.handleActivityConfigurationChanged(getActivityClientRecord(activity),
config, INVALID_DISPLAY);
});
@@ -472,10 +471,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.",
@@ -560,9 +559,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 +575,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 +589,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 +601,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 +616,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 +631,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 */);
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/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/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index a2b1e3d69cd2..8412e53060c7 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -18,7 +18,6 @@ package android.view;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
-import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
import static android.view.WindowInsets.Type.systemBars;
import static org.junit.Assert.assertEquals;
@@ -42,13 +41,10 @@ import android.util.SparseArray;
import android.view.SurfaceControl.Transaction;
import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.animation.LinearInterpolator;
-import android.view.test.InsetsModeSession;
import androidx.test.runner.AndroidJUnit4;
-import org.junit.AfterClass;
import org.junit.Before;
-import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -76,22 +72,11 @@ public class InsetsAnimationControlImplTest {
private SurfaceControl mTopLeash;
private SurfaceControl mNavLeash;
private InsetsState mInsetsState;
- private static InsetsModeSession sInsetsModeSession;
@Mock Transaction mMockTransaction;
@Mock InsetsController mMockController;
@Mock WindowInsetsAnimationControlListener mMockListener;
- @BeforeClass
- public static void setupOnce() {
- sInsetsModeSession = new InsetsModeSession(NEW_INSETS_MODE_FULL);
- }
-
- @AfterClass
- public static void tearDownOnce() {
- sInsetsModeSession.close();
- }
-
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index 88e1f5781e0e..cc97eb5988e6 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -26,7 +26,6 @@ import static android.view.InsetsState.ITYPE_CAPTION_BAR;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
-import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.statusBars;
@@ -63,7 +62,6 @@ import android.view.WindowInsetsController.OnControllableInsetsChangedListener;
import android.view.WindowManager.BadTokenException;
import android.view.WindowManager.LayoutParams;
import android.view.animation.LinearInterpolator;
-import android.view.test.InsetsModeSession;
import android.widget.TextView;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -72,9 +70,7 @@ import androidx.test.platform.app.InstrumentationRegistry;
import com.android.server.testutils.OffsettableClock;
import com.android.server.testutils.TestHandler;
-import org.junit.AfterClass;
import org.junit.Before;
-import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -102,17 +98,6 @@ public class InsetsControllerTest {
private TestHost mTestHost;
private TestHandler mTestHandler;
private OffsettableClock mTestClock;
- private static InsetsModeSession sInsetsModeSession;
-
- @BeforeClass
- public static void setupOnce() {
- sInsetsModeSession = new InsetsModeSession(NEW_INSETS_MODE_FULL);
- }
-
- @AfterClass
- public static void tearDownOnce() {
- sInsetsModeSession.close();
- }
@Before
public void setup() {
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index 4306e3e0fe7b..afab7696e87d 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -31,6 +31,7 @@ import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.statusBars;
+import static android.view.WindowInsets.Type.systemBars;
import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
@@ -51,7 +52,6 @@ import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
import android.util.SparseIntArray;
import android.view.WindowInsets.Type;
-import android.view.test.InsetsModeSession;
import androidx.test.runner.AndroidJUnit4;
@@ -75,114 +75,96 @@ public class InsetsStateTest {
private InsetsState mState2 = new InsetsState();
@Test
- public void testCalculateInsets() throws Exception {
- try (final InsetsModeSession session =
- new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
- mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
- mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
- mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
- mState.getSource(ITYPE_IME).setVisible(true);
- SparseIntArray typeSideMap = new SparseIntArray();
- WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
- false, DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_RESIZE, 0, 0,
- TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, typeSideMap);
- assertEquals(Insets.of(0, 100, 0, 100), insets.getSystemWindowInsets());
- assertEquals(Insets.of(0, 100, 0, 100), insets.getInsets(Type.all()));
- assertEquals(ISIDE_TOP, typeSideMap.get(ITYPE_STATUS_BAR));
- assertEquals(ISIDE_BOTTOM, typeSideMap.get(ITYPE_IME));
- assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(Type.statusBars()));
- assertEquals(Insets.of(0, 0, 0, 100), insets.getInsets(Type.ime()));
- }
+ public void testCalculateInsets() {
+ mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
+ mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
+ mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
+ mState.getSource(ITYPE_IME).setVisible(true);
+ SparseIntArray typeSideMap = new SparseIntArray();
+ WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
+ false, DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_RESIZE, 0, 0,
+ TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, typeSideMap);
+ assertEquals(Insets.of(0, 100, 0, 100), insets.getSystemWindowInsets());
+ assertEquals(Insets.of(0, 100, 0, 100), insets.getInsets(Type.all()));
+ assertEquals(ISIDE_TOP, typeSideMap.get(ITYPE_STATUS_BAR));
+ assertEquals(ISIDE_BOTTOM, typeSideMap.get(ITYPE_IME));
+ assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(statusBars()));
+ assertEquals(Insets.of(0, 0, 0, 100), insets.getInsets(ime()));
}
@Test
- public void testCalculateInsets_imeAndNav() throws Exception{
- try (final InsetsModeSession session =
- new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
- mState.getSource(ITYPE_NAVIGATION_BAR).setFrame(new Rect(0, 200, 100, 300));
- mState.getSource(ITYPE_NAVIGATION_BAR).setVisible(true);
- mState.getSource(ITYPE_IME).setFrame(new Rect(0, 100, 100, 300));
- mState.getSource(ITYPE_IME).setVisible(true);
- WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
- false, DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_RESIZE, 0, 0,
- TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
- assertEquals(100, insets.getStableInsetBottom());
- assertEquals(Insets.of(0, 0, 0, 100), insets.getInsetsIgnoringVisibility(Type.systemBars()));
- assertEquals(Insets.of(0, 0, 0, 200), insets.getSystemWindowInsets());
- assertEquals(Insets.of(0, 0, 0, 200), insets.getInsets(Type.all()));
- assertEquals(Insets.of(0, 0, 0, 100), insets.getInsets(Type.navigationBars()));
- assertEquals(Insets.of(0, 0, 0, 200), insets.getInsets(Type.ime()));
- }
+ public void testCalculateInsets_imeAndNav() {
+ mState.getSource(ITYPE_NAVIGATION_BAR).setFrame(new Rect(0, 200, 100, 300));
+ mState.getSource(ITYPE_NAVIGATION_BAR).setVisible(true);
+ mState.getSource(ITYPE_IME).setFrame(new Rect(0, 100, 100, 300));
+ mState.getSource(ITYPE_IME).setVisible(true);
+ WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
+ false, DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_RESIZE, 0, 0,
+ TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+ assertEquals(100, insets.getStableInsetBottom());
+ assertEquals(Insets.of(0, 0, 0, 100), insets.getInsetsIgnoringVisibility(systemBars()));
+ assertEquals(Insets.of(0, 0, 0, 200), insets.getSystemWindowInsets());
+ assertEquals(Insets.of(0, 0, 0, 200), insets.getInsets(Type.all()));
+ assertEquals(Insets.of(0, 0, 0, 100), insets.getInsets(navigationBars()));
+ assertEquals(Insets.of(0, 0, 0, 200), insets.getInsets(ime()));
}
@Test
- public void testCalculateInsets_navRightStatusTop() throws Exception {
- try (final InsetsModeSession session =
- new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
- mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
- mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
- mState.getSource(ITYPE_NAVIGATION_BAR).setFrame(new Rect(80, 0, 100, 300));
- mState.getSource(ITYPE_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);
- 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()));
- }
+ public void testCalculateInsets_navRightStatusTop() {
+ mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
+ mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
+ mState.getSource(ITYPE_NAVIGATION_BAR).setFrame(new Rect(80, 0, 100, 300));
+ mState.getSource(ITYPE_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);
+ assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets());
+ assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(statusBars()));
+ assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(navigationBars()));
}
@Test
public void testCalculateInsets_imeIgnoredWithoutAdjustResize() {
- try (final InsetsModeSession session =
- new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
- mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
- mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
- mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
- mState.getSource(ITYPE_IME).setVisible(true);
- WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
- false, DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_NOTHING, 0, 0,
- TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
- assertEquals(0, insets.getSystemWindowInsetBottom());
- assertEquals(100, insets.getInsets(ime()).bottom);
- assertTrue(insets.isVisible(ime()));
- }
+ mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
+ mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
+ mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
+ mState.getSource(ITYPE_IME).setVisible(true);
+ WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
+ false, DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_NOTHING, 0, 0,
+ TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+ assertEquals(0, insets.getSystemWindowInsetBottom());
+ assertEquals(100, insets.getInsets(ime()).bottom);
+ assertTrue(insets.isVisible(ime()));
}
@Test
public void testCalculateInsets_systemUiFlagLayoutStable() {
- try (final InsetsModeSession session =
- new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
- mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
- mState.getSource(ITYPE_STATUS_BAR).setVisible(false);
- mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
- mState.getSource(ITYPE_IME).setVisible(true);
- WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
- false, DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_NOTHING, 0,
- SYSTEM_UI_FLAG_LAYOUT_STABLE, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
- assertEquals(100, insets.getSystemWindowInsetTop());
- insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, false,
- DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_NOTHING, 0,
- 0 /* legacySystemUiFlags */, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
- assertEquals(0, insets.getSystemWindowInsetTop());
- }
+ mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
+ mState.getSource(ITYPE_STATUS_BAR).setVisible(false);
+ mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
+ mState.getSource(ITYPE_IME).setVisible(true);
+ WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
+ false, DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_NOTHING, 0,
+ SYSTEM_UI_FLAG_LAYOUT_STABLE, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+ assertEquals(100, insets.getSystemWindowInsetTop());
+ insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, false,
+ DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_NOTHING, 0,
+ 0 /* legacySystemUiFlags */, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+ assertEquals(0, insets.getSystemWindowInsetTop());
}
@Test
public void testCalculateInsets_systemUiFlagLayoutStable_windowFlagFullscreen() {
- try (final InsetsModeSession session =
- new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
- mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
- mState.getSource(ITYPE_STATUS_BAR).setVisible(false);
- WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
- false, DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_NOTHING, FLAG_FULLSCREEN,
- SYSTEM_UI_FLAG_LAYOUT_STABLE, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
- assertEquals(0, insets.getSystemWindowInsetTop());
- insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, false,
- DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_NOTHING, 0,
- 0 /* legacySystemUiFlags */, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
- assertEquals(0, insets.getSystemWindowInsetTop());
- }
+ mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
+ mState.getSource(ITYPE_STATUS_BAR).setVisible(false);
+ WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
+ false, DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_NOTHING, FLAG_FULLSCREEN,
+ SYSTEM_UI_FLAG_LAYOUT_STABLE, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+ assertEquals(0, insets.getSystemWindowInsetTop());
+ insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, false,
+ DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_NOTHING, 0,
+ 0 /* legacySystemUiFlags */, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+ assertEquals(0, insets.getSystemWindowInsetTop());
}
@Test
@@ -209,65 +191,53 @@ public class InsetsStateTest {
@Test
- public void testCalculateInsets_captionStatusBarOverlap() throws Exception {
- try (InsetsModeSession session =
- new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
- mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
- mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
- mState.getSource(ITYPE_CAPTION_BAR).setFrame(new Rect(0, 0, 100, 300));
- mState.getSource(ITYPE_CAPTION_BAR).setVisible(true);
-
- Rect visibleInsets = mState.calculateVisibleInsets(
- new Rect(0, 0, 100, 400), SOFT_INPUT_ADJUST_NOTHING);
- assertEquals(new Rect(0, 300, 0, 0), visibleInsets);
- }
+ public void testCalculateInsets_captionStatusBarOverlap() {
+ mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
+ mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
+ mState.getSource(ITYPE_CAPTION_BAR).setFrame(new Rect(0, 0, 100, 300));
+ mState.getSource(ITYPE_CAPTION_BAR).setVisible(true);
+
+ Rect visibleInsets = mState.calculateVisibleInsets(
+ new Rect(0, 0, 100, 400), SOFT_INPUT_ADJUST_NOTHING);
+ assertEquals(new Rect(0, 300, 0, 0), visibleInsets);
}
@Test
- public void testCalculateInsets_captionBarOffset() throws Exception {
- try (InsetsModeSession session =
- new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
- mState.getSource(ITYPE_CAPTION_BAR).setFrame(new Rect(0, 0, 100, 300));
- mState.getSource(ITYPE_CAPTION_BAR).setVisible(true);
-
- Rect visibleInsets = mState.calculateVisibleInsets(
- new Rect(0, 0, 150, 400), SOFT_INPUT_ADJUST_NOTHING);
- assertEquals(new Rect(0, 300, 0, 0), visibleInsets);
- }
+ public void testCalculateInsets_captionBarOffset() {
+ mState.getSource(ITYPE_CAPTION_BAR).setFrame(new Rect(0, 0, 100, 300));
+ mState.getSource(ITYPE_CAPTION_BAR).setVisible(true);
+
+ Rect visibleInsets = mState.calculateVisibleInsets(
+ new Rect(0, 0, 150, 400), SOFT_INPUT_ADJUST_NOTHING);
+ assertEquals(new Rect(0, 300, 0, 0), visibleInsets);
}
@Test
- public void testCalculateInsets_extraNavRightStatusTop() throws Exception {
- try (InsetsModeSession session =
- new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
- mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
- mState.getSource(ITYPE_STATUS_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);
- 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()));
- }
+ public void testCalculateInsets_extraNavRightStatusTop() {
+ mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
+ mState.getSource(ITYPE_STATUS_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);
+ assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets());
+ assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(statusBars()));
+ assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(navigationBars()));
}
@Test
- public void testCalculateInsets_navigationRightClimateTop() throws Exception {
- try (InsetsModeSession session =
- new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
- mState.getSource(ITYPE_CLIMATE_BAR).setFrame(new Rect(0, 0, 100, 100));
- mState.getSource(ITYPE_CLIMATE_BAR).setVisible(true);
- mState.getSource(ITYPE_NAVIGATION_BAR).setFrame(new Rect(80, 0, 100, 300));
- mState.getSource(ITYPE_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);
- 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()));
- }
+ public void testCalculateInsets_navigationRightClimateTop() {
+ mState.getSource(ITYPE_CLIMATE_BAR).setFrame(new Rect(0, 0, 100, 100));
+ mState.getSource(ITYPE_CLIMATE_BAR).setVisible(true);
+ mState.getSource(ITYPE_NAVIGATION_BAR).setFrame(new Rect(80, 0, 100, 300));
+ mState.getSource(ITYPE_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);
+ assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets());
+ assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(statusBars()));
+ assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(navigationBars()));
}
@Test
@@ -371,59 +341,51 @@ public class InsetsStateTest {
}
@Test
- public void testCalculateVisibleInsets() throws Exception {
- try (final InsetsModeSession session =
- new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
- mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
- mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
- mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
- mState.getSource(ITYPE_IME).setVisible(true);
-
- // Make sure bottom gestures are ignored
- mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300));
- mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true);
- Rect visibleInsets = mState.calculateVisibleInsets(
- new Rect(0, 0, 100, 300), SOFT_INPUT_ADJUST_PAN);
- assertEquals(new Rect(0, 100, 0, 100), visibleInsets);
- }
+ public void testCalculateVisibleInsets() {
+ mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
+ mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
+ mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
+ mState.getSource(ITYPE_IME).setVisible(true);
+
+ // Make sure bottom gestures are ignored
+ mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300));
+ mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true);
+ Rect visibleInsets = mState.calculateVisibleInsets(
+ new Rect(0, 0, 100, 300), SOFT_INPUT_ADJUST_PAN);
+ assertEquals(new Rect(0, 100, 0, 100), visibleInsets);
}
@Test
- public void testCalculateVisibleInsets_adjustNothing() throws Exception {
- try (final InsetsModeSession session =
- new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
- mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
- mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
- mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
- mState.getSource(ITYPE_IME).setVisible(true);
-
- // Make sure bottom gestures are ignored
- mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300));
- mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true);
- Rect visibleInsets = mState.calculateVisibleInsets(
- new Rect(0, 0, 100, 300), SOFT_INPUT_ADJUST_NOTHING);
- assertEquals(new Rect(0, 100, 0, 0), visibleInsets);
- }
+ public void testCalculateVisibleInsets_adjustNothing() {
+ mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
+ mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
+ mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
+ mState.getSource(ITYPE_IME).setVisible(true);
+
+ // Make sure bottom gestures are ignored
+ mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300));
+ mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true);
+ Rect visibleInsets = mState.calculateVisibleInsets(
+ new Rect(0, 0, 100, 300), SOFT_INPUT_ADJUST_NOTHING);
+ assertEquals(new Rect(0, 100, 0, 0), visibleInsets);
}
@Test
- public void testCalculateUncontrollableInsets() throws Exception {
- try (InsetsModeSession session = new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
- mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 200, 100));
- mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
- mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 200, 300));
- mState.getSource(ITYPE_IME).setVisible(true);
- mState.getSource(ITYPE_NAVIGATION_BAR).setFrame(new Rect(100, 0, 200, 300));
- mState.getSource(ITYPE_NAVIGATION_BAR).setVisible(true);
-
- mState.setDisplayFrame(new Rect(0, 0, 200, 300));
- assertEquals(0,
- mState.calculateUncontrollableInsetsFromFrame(new Rect(0, 0, 200, 300)));
- assertEquals(statusBars() | ime(),
- mState.calculateUncontrollableInsetsFromFrame(new Rect(0, 50, 200, 250)));
- assertEquals(navigationBars(),
- mState.calculateUncontrollableInsetsFromFrame(new Rect(50, 0, 150, 300)));
- }
+ public void testCalculateUncontrollableInsets() {
+ mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 200, 100));
+ mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
+ mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 200, 300));
+ mState.getSource(ITYPE_IME).setVisible(true);
+ mState.getSource(ITYPE_NAVIGATION_BAR).setFrame(new Rect(100, 0, 200, 300));
+ mState.getSource(ITYPE_NAVIGATION_BAR).setVisible(true);
+
+ mState.setDisplayFrame(new Rect(0, 0, 200, 300));
+ assertEquals(0,
+ mState.calculateUncontrollableInsetsFromFrame(new Rect(0, 0, 200, 300)));
+ assertEquals(statusBars() | ime(),
+ mState.calculateUncontrollableInsetsFromFrame(new Rect(0, 50, 200, 250)));
+ assertEquals(navigationBars(),
+ mState.calculateUncontrollableInsetsFromFrame(new Rect(50, 0, 150, 300)));
}
private void assertEqualsAndHashCode() {
diff --git a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
index 93de03adfa84..5c410878c99d 100644
--- a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
+++ b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
@@ -274,13 +274,10 @@ public class EditorInfoTest {
editorInfo.inputType = EditorInfo.TYPE_CLASS_TEXT;
editorInfo.setInitialSurroundingText(sb);
- sb.setLength(0);
- editorInfo.writeToParcel(parcel, 0);
+ sb.setLength(/* newLength= */ 0);
+ editorInfo.writeToParcel(parcel, /* flags= */ 0);
- try {
- editorInfo.getInitialTextBeforeCursor(60, 1);
- fail("Test shouldn't have exception");
- } catch (AssertionError e) { }
+ editorInfo.getInitialTextBeforeCursor(/* length= */ 60, /* flags= */ 1);
}
private static void assertExpectedTextLength(EditorInfo editorInfo,
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/PowerStatsLoadTests/AndroidManifest.xml b/core/tests/powertests/PowerStatsLoadTests/AndroidManifest.xml
index b1c2a639aff4..9cecaedf1380 100644
--- a/core/tests/powertests/PowerStatsLoadTests/AndroidManifest.xml
+++ b/core/tests/powertests/PowerStatsLoadTests/AndroidManifest.xml
@@ -22,6 +22,7 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.BATTERY_STATS"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
+ <uses-permission android:name="android.permission.DEVICE_POWER"/>
<uses-permission android:name="android.permission.INTERNET"/>
<instrumentation
diff --git a/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/PowerMetricsCollector.java b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/PowerMetricsCollector.java
index 0cdb404f6aaa..a71559b5ad6b 100644
--- a/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/PowerMetricsCollector.java
+++ b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/PowerMetricsCollector.java
@@ -16,6 +16,7 @@
package com.android.frameworks.core.powerstatsloadtests;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.app.Instrumentation;
@@ -34,6 +35,7 @@ import android.util.TimeUtils;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.compatibility.common.util.SystemUtil;
import com.android.internal.os.BatteryStatsHelper;
import com.android.internal.os.LoggingPrintStream;
@@ -45,6 +47,8 @@ import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
public class PowerMetricsCollector implements TestRule {
private final String mTag;
@@ -55,6 +59,7 @@ public class PowerMetricsCollector implements TestRule {
private final UserManager mUserManager;
private final int mUid;
private final BatteryStatsHelper mStatsHelper;
+ private final CountDownLatch mSuspendingBatteryInput = new CountDownLatch(1);
private long mStartTime;
private volatile float mInitialBatteryLevel;
@@ -63,12 +68,21 @@ public class PowerMetricsCollector implements TestRule {
private PowerMetrics mInitialPowerMetrics;
private PowerMetrics mFinalPowerMetrics;
private List<PowerMetrics.Metric> mPowerMetricsDelta;
+ private Intent mBatteryStatus;
@Override
public Statement apply(Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
+ BroadcastReceiver batteryBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ handleBatteryStatus(intent);
+ }
+ };
+ mBatteryStatus = mContext.registerReceiver(batteryBroadcastReceiver,
+ new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
disableCharger();
try {
prepareBatteryLevelMonitor();
@@ -76,6 +90,7 @@ public class PowerMetricsCollector implements TestRule {
base.evaluate();
captureFinalPowerStatsData();
} finally {
+ mContext.unregisterReceiver(batteryBroadcastReceiver);
enableCharger();
}
}
@@ -95,12 +110,14 @@ public class PowerMetricsCollector implements TestRule {
mStatsHelper.create((Bundle) null);
}
- private void disableCharger() {
- // TODO(b/167636754): implement this method once the charger suspension API is available
+ private void disableCharger() throws InterruptedException {
+ SystemUtil.runShellCommand("dumpsys battery suspend_input");
+ final boolean success = mSuspendingBatteryInput.await(10, TimeUnit.SECONDS);
+ assertTrue("Timed out waiting for battery input to be suspended", success);
}
private void enableCharger() {
- // TODO(b/167636754): implement this method once the charger suspension API is available
+ SystemUtil.runShellCommand("dumpsys battery reset");
}
private PowerMetrics readBatteryStatsData() {
@@ -111,19 +128,25 @@ public class PowerMetricsCollector implements TestRule {
}
protected void prepareBatteryLevelMonitor() {
- Intent batteryStatus = mContext.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- handleBatteryStatus(intent);
- }
- }, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
-
- handleBatteryStatus(batteryStatus);
+ handleBatteryStatus(mBatteryStatus);
mInitialBatteryLevel = mCurrentBatteryLevel;
}
protected void handleBatteryStatus(Intent intent) {
- if (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) != 0) {
+ if (mFinalPowerMetrics != null) {
+ return;
+ }
+
+ final boolean isCharging = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) != 0;
+
+ if (mSuspendingBatteryInput.getCount() > 0) {
+ if (!isCharging) {
+ mSuspendingBatteryInput.countDown();
+ }
+ return;
+ }
+
+ if (isCharging) {
fail("Device must remain disconnected from the power source "
+ "for the duration of the test");
}
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index dd8f40d586bc..0a0681479278 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -153,8 +153,8 @@
<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.INTERNET" uid="media" />
+ <assign-permission name="android.permission.INTERACT_ACROSS_USERS" uid="media" />
<assign-permission name="android.permission.INTERNET" uid="shell" />
@@ -164,6 +164,7 @@
<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" />
@@ -174,8 +175,10 @@
<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" />
@@ -190,8 +193,10 @@
<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" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 602ccfeca2f2..102c933dd84d 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -226,6 +226,7 @@ applications that come with the platform
<permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<permission name="android.permission.UPDATE_APP_OPS_STATS"/>
<permission name="android.permission.UPDATE_DEVICE_STATS"/>
+ <permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
</privapp-permissions>
<privapp-permissions package="com.android.providers.media.module">
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 217de84cfc3e..6b80bb60ec67 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -259,6 +259,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/WindowState.java"
},
+ "-1741065110": {
+ "message": "No app is requesting an orientation, return %d for display id=%d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"-1730156332": {
"message": "Display id=%d rotation changed to %d from %d, lastOrientation=%d",
"level": "VERBOSE",
@@ -1909,6 +1915,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowToken.java"
},
+ "845234215": {
+ "message": "App is requesting an orientation, return %d for display id=%d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"853091290": {
"message": "Moved stack=%s behind stack=%s",
"level": "DEBUG",
@@ -2179,12 +2191,6 @@
"group": "WM_DEBUG_IME",
"at": "com\/android\/server\/wm\/ImeInsetsSourceProvider.java"
},
- "1381227466": {
- "message": "App is requesting an orientation, return %d for display id=%d",
- "level": "VERBOSE",
- "group": "WM_DEBUG_ORIENTATION",
- "at": "com\/android\/server\/wm\/TaskDisplayArea.java"
- },
"1401295262": {
"message": "Mode default, asking user",
"level": "WARN",
@@ -2371,12 +2377,6 @@
"group": "WM_DEBUG_RESIZE",
"at": "com\/android\/server\/wm\/WindowState.java"
},
- "1640436199": {
- "message": "No app is requesting an orientation, return %d for display id=%d",
- "level": "VERBOSE",
- "group": "WM_DEBUG_ORIENTATION",
- "at": "com\/android\/server\/wm\/TaskDisplayArea.java"
- },
"1653210583": {
"message": "Removing app %s delayed=%b animation=%s animating=%b",
"level": "VERBOSE",
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 4c214b529b39..c992bd8f8338 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -142,11 +142,30 @@
<font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font>
</family>
<family lang="und-Armn">
- <font weight="400" style="normal">NotoSansArmenian-Regular.otf</font>
- <font weight="500" style="normal">NotoSansArmenian-Medium.otf</font>
- <font weight="700" style="normal">NotoSansArmenian-Bold.otf</font>
- <font weight="400" style="normal" fallbackFor="serif">NotoSerifArmenian-Regular.otf</font>
- <font weight="700" style="normal" fallbackFor="serif">NotoSerifArmenian-Bold.otf</font>
+ <font weight="400" style="normal">NotoSansArmenian-VF.ttf
+ <axis tag="wght" stylevalue="400" />
+ </font>
+ <font weight="500" style="normal">NotoSansArmenian-VF.ttf
+ <axis tag="wght" stylevalue="500" />
+ </font>
+ <font weight="600" style="normal">NotoSansArmenian-VF.ttf
+ <axis tag="wght" stylevalue="600" />
+ </font>
+ <font weight="700" style="normal">NotoSansArmenian-VF.ttf
+ <axis tag="wght" stylevalue="700" />
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif">NotoSerifArmenian-VF.ttf
+ <axis tag="wght" stylevalue="400" />
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif">NotoSerifArmenian-VF.ttf
+ <axis tag="wght" stylevalue="500" />
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif">NotoSerifArmenian-VF.ttf
+ <axis tag="wght" stylevalue="600" />
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifArmenian-VF.ttf
+ <axis tag="wght" stylevalue="700" />
+ </font>
</family>
<family lang="und-Geor,und-Geok">
<font weight="400" style="normal">NotoSansGeorgian-VF.ttf
diff --git a/data/keyboards/Vendor_27f8_Product_0bbe.kl b/data/keyboards/Vendor_27f8_Product_0bbe.kl
new file mode 100644
index 000000000000..211e532c8326
--- /dev/null
+++ b/data/keyboards/Vendor_27f8_Product_0bbe.kl
@@ -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.
+
+#
+# Gamevice GV186 Mobile Controller
+#
+
+
+# Mapping according to https://developer.android.com/training/game-controllers/controller-input.html
+
+key 0x133 BUTTON_X
+key 0x130 BUTTON_A
+key 0x131 BUTTON_B
+key 0x134 BUTTON_Y
+
+key 0x136 BUTTON_L1
+key 0x137 BUTTON_R1
+key 0x138 BUTTON_L2
+key 0x139 BUTTON_R2
+
+axis 0x00 X
+axis 0x01 Y
+
+axis 0x02 Z
+axis 0x05 RZ
+
+axis 0x09 RTRIGGER
+axis 0x0a LTRIGGER
+
+key 0x13d BUTTON_THUMBL
+key 0x13e BUTTON_THUMBR
+
+# Hat
+axis 0x10 HAT_X
+axis 0x11 HAT_Y
+
+# Mapping according to https://www.kernel.org/doc/Documentation/input/gamepad.txt
+# Left Triangle Button
+key 0x13a BUTTON_SELECT
+# Right Triangle Button
+key 0x13b BUTTON_START
+# Home Button
+key 0x13c BUTTON_MODE
diff --git a/graphics/java/android/graphics/FrameInfo.java b/graphics/java/android/graphics/FrameInfo.java
index 42a5cc43eaa0..163823fde9f7 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[9];
+ public long[] frameInfo = new long[10];
// Various flags set to provide extra metadata about the current frame
private static final int FLAGS = 0;
@@ -51,38 +51,44 @@ public final class FrameInfo {
// A renderer associated with just a Surface, not with a ViewRootImpl instance.
public static final long FLAG_SURFACE_CANVAS = 1 << 2;
+ // An invalid vsync id to be used when FRAME_TIMELINE_VSYNC_ID is unknown
+ public static final long INVALID_VSYNC_ID = -1;
+
@LongDef(flag = true, value = {
FLAG_WINDOW_LAYOUT_CHANGED, FLAG_SURFACE_CANVAS })
@Retention(RetentionPolicy.SOURCE)
public @interface FrameInfoFlags {}
+ private static final int FRAME_TIMELINE_VSYNC_ID = 1;
+
// The intended vsync time, unadjusted by jitter
- private static final int INTENDED_VSYNC = 1;
+ private static final int INTENDED_VSYNC = 2;
// Jitter-adjusted vsync time, this is what was used as input into the
// animation & drawing system
- private static final int VSYNC = 2;
+ private static final int VSYNC = 3;
// The time of the oldest input event
- private static final int OLDEST_INPUT_EVENT = 3;
+ private static final int OLDEST_INPUT_EVENT = 4;
// The time of the newest input event
- private static final int NEWEST_INPUT_EVENT = 4;
+ private static final int NEWEST_INPUT_EVENT = 5;
// When input event handling started
- private static final int HANDLE_INPUT_START = 5;
+ private static final int HANDLE_INPUT_START = 6;
// When animation evaluations started
- private static final int ANIMATION_START = 6;
+ private static final int ANIMATION_START = 7;
// When ViewRootImpl#performTraversals() started
- private static final int PERFORM_TRAVERSALS_START = 7;
+ private static final int PERFORM_TRAVERSALS_START = 8;
// When View:draw() started
- private static final int DRAW_START = 8;
+ private static final int DRAW_START = 9;
/** checkstyle */
- public void setVsync(long intendedVsync, long usedVsync) {
+ public void setVsync(long intendedVsync, long usedVsync, long frameTimelineVsyncId) {
+ frameInfo[FRAME_TIMELINE_VSYNC_ID] = frameTimelineVsyncId;
frameInfo[INTENDED_VSYNC] = intendedVsync;
frameInfo[VSYNC] = usedVsync;
frameInfo[OLDEST_INPUT_EVENT] = Long.MAX_VALUE;
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index 0452933328e2..fd5916c2158d 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -354,7 +354,8 @@ public class HardwareRenderer {
* @return this instance
*/
public @NonNull FrameRenderRequest setVsyncTime(long vsyncTime) {
- mFrameInfo.setVsync(vsyncTime, vsyncTime);
+ // TODO(b/168552873): populate vsync Id once available to Choreographer public API
+ mFrameInfo.setVsync(vsyncTime, vsyncTime, FrameInfo.INVALID_VSYNC_ID);
mFrameInfo.addFlags(FrameInfo.FLAG_SURFACE_CANVAS);
return this;
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SettingsSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SettingsSidecarImpl.java
index ca3a5112bc55..5397302f6882 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SettingsSidecarImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SettingsSidecarImpl.java
@@ -37,6 +37,8 @@ import android.util.Log;
import androidx.annotation.NonNull;
+import com.android.internal.R;
+
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
@@ -151,13 +153,18 @@ class SettingsSidecarImpl extends StubSidecar {
return features;
}
- ContentResolver resolver = mContext.getContentResolver();
- final String displayFeaturesString = Settings.Global.getString(resolver, DISPLAY_FEATURES);
if (isInMultiWindow(windowToken)) {
// It is recommended not to report any display features in multi-window mode, since it
// won't be possible to synchronize the display feature positions with window movement.
return features;
}
+
+ ContentResolver resolver = mContext.getContentResolver();
+ String displayFeaturesString = Settings.Global.getString(resolver, DISPLAY_FEATURES);
+ if (TextUtils.isEmpty(displayFeaturesString)) {
+ displayFeaturesString = mContext.getResources().getString(
+ R.string.config_display_features);
+ }
if (TextUtils.isEmpty(displayFeaturesString)) {
return features;
}
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 283fd8d997c9..c18e9ce76153 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
@@ -178,7 +178,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
}
- class PerDisplay extends IDisplayWindowInsetsController.Stub {
+ /** An implementation of {@link IDisplayWindowInsetsController} for a given display id. */
+ public class PerDisplay extends IDisplayWindowInsetsController.Stub {
final int mDisplayId;
final InsetsState mInsetsState = new InsetsState();
InsetsSourceControl mImeSourceControl = null;
@@ -189,7 +190,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
final Rect mImeFrame = new Rect();
boolean mAnimateAlpha = true;
- PerDisplay(int displayId, int initialRotation) {
+ public PerDisplay(int displayId, int initialRotation) {
mDisplayId = displayId;
mRotation = initialRotation;
}
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 c84b4781d19d..d060f6444463 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
@@ -172,8 +172,10 @@ public class OneHandedController implements OneHanded {
context, displayController);
OneHandedDisplayAreaOrganizer organizer = new OneHandedDisplayAreaOrganizer(
context, displayController, animationController, tutorialHandler);
+ IOverlayManager overlayManager = IOverlayManager.Stub.asInterface(
+ ServiceManager.getService(Context.OVERLAY_SERVICE));
return new OneHandedController(context, displayController, organizer, touchHandler,
- tutorialHandler, gestureHandler);
+ tutorialHandler, gestureHandler, overlayManager);
}
@VisibleForTesting
@@ -182,7 +184,8 @@ public class OneHandedController implements OneHanded {
OneHandedDisplayAreaOrganizer displayAreaOrganizer,
OneHandedTouchHandler touchHandler,
OneHandedTutorialHandler tutorialHandler,
- OneHandedGestureHandler gestureHandler) {
+ OneHandedGestureHandler gestureHandler,
+ IOverlayManager overlayManager) {
mHasOneHandedFeature = SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false);
if (!mHasOneHandedFeature) {
Log.i(TAG, "Device config SUPPORT_ONE_HANDED_MODE off");
@@ -194,32 +197,32 @@ public class OneHandedController implements OneHanded {
mGestureHandler = null;
mTimeoutHandler = null;
mOverlayManager = null;
- return;
+ } 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 = IOverlayManager.Stub.asInterface(
- ServiceManager.getService(Context.OVERLAY_SERVICE));
- 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();
}
/**
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
index 1ce8b5445b37..3645f1e56f92 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
@@ -18,13 +18,13 @@ package com.android.wm.shell.onehanded;
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.om.IOverlayManager;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -62,6 +62,8 @@ public class OneHandedControllerTest extends OneHandedTestCase {
OneHandedGestureHandler mMockGestureHandler;
@Mock
OneHandedTimeoutHandler mMockTimeoutHandler;
+ @Mock
+ IOverlayManager mMockOverlayManager;
@Before
public void setUp() throws Exception {
@@ -73,7 +75,8 @@ public class OneHandedControllerTest extends OneHandedTestCase {
mMockDisplayAreaOrganizer,
mMockTouchHandler,
mMockTutorialHandler,
- mMockGestureHandler);
+ mMockGestureHandler,
+ mMockOverlayManager);
mOneHandedController = Mockito.spy(oneHandedController);
mTimeoutHandler = Mockito.spy(OneHandedTimeoutHandler.get());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
index 4a133d39291a..3341c9cbacb9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.onehanded;
import static org.mockito.Mockito.verify;
+import android.content.om.IOverlayManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -44,6 +45,8 @@ public class OneHandedTutorialHandlerTest extends OneHandedTestCase {
DisplayController mMockDisplayController;
@Mock
OneHandedDisplayAreaOrganizer mMockDisplayAreaOrganizer;
+ @Mock
+ IOverlayManager mMockOverlayManager;
@Before
public void setUp() {
@@ -56,11 +59,12 @@ public class OneHandedTutorialHandlerTest extends OneHandedTestCase {
mMockDisplayAreaOrganizer,
mTouchHandler,
mTutorialHandler,
- mGestureHandler);
+ mGestureHandler,
+ mMockOverlayManager);
}
@Test
- public void testOneHandedManager_registerForDisplayAreaOrganizer() {
+ public void testRegisterForDisplayAreaOrganizer() {
verify(mMockDisplayAreaOrganizer).registerTransitionCallback(mTutorialHandler);
}
}
diff --git a/libs/hwui/FrameInfo.cpp b/libs/hwui/FrameInfo.cpp
index 0698775b0021..30ce5370760d 100644
--- a/libs/hwui/FrameInfo.cpp
+++ b/libs/hwui/FrameInfo.cpp
@@ -22,6 +22,7 @@ namespace uirenderer {
const std::string FrameInfoNames[] = {
"Flags",
+ "FrameTimelineVsyncId",
"IntendedVsync",
"Vsync",
"OldestInputEvent",
@@ -44,7 +45,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) == 17,
+static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 18,
"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 dc30617009e7..f5bfedde2f92 100644
--- a/libs/hwui/FrameInfo.h
+++ b/libs/hwui/FrameInfo.h
@@ -27,10 +27,11 @@
namespace android {
namespace uirenderer {
-#define UI_THREAD_FRAME_INFO_SIZE 9
+#define UI_THREAD_FRAME_INFO_SIZE 10
enum class FrameInfoIndex {
Flags = 0,
+ FrameTimelineVsyncId,
IntendedVsync,
Vsync,
OldestInputEvent,
@@ -71,11 +72,15 @@ enum {
class UiFrameInfoBuilder {
public:
+ static constexpr int64_t INVALID_VSYNC_ID = -1;
+
explicit UiFrameInfoBuilder(int64_t* buffer) : mBuffer(buffer) {
memset(mBuffer, 0, UI_THREAD_FRAME_INFO_SIZE * sizeof(int64_t));
+ set(FrameInfoIndex::FrameTimelineVsyncId) = INVALID_VSYNC_ID;
}
- UiFrameInfoBuilder& setVsync(nsecs_t vsyncTime, nsecs_t intendedVsync) {
+ UiFrameInfoBuilder& setVsync(nsecs_t vsyncTime, nsecs_t intendedVsync, int64_t vsyncId) {
+ set(FrameInfoIndex::FrameTimelineVsyncId) = vsyncId;
set(FrameInfoIndex::Vsync) = vsyncTime;
set(FrameInfoIndex::IntendedVsync) = intendedVsync;
// Pretend the other fields are all at vsync, too, so that naive
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index e817ca744c58..c89463bf3ab4 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -514,7 +514,7 @@ 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)
+ .setVsync(vsync, vsync, UiFrameInfoBuilder::INVALID_VSYNC_ID)
.addFlag(FrameInfoFlags::SurfaceCanvas);
proxy.syncAndDrawFrame();
}
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index dd0fc695c246..bfbdc5c009c0 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -56,7 +56,8 @@ static void GetFboDetails(SkCanvas* canvas, GLuint* outFboID, SkISize* outFboSiz
}
void GLFunctorDrawable::onDraw(SkCanvas* canvas) {
- if (canvas->getGrContext() == nullptr) {
+ GrDirectContext* directContext = GrAsDirectContext(canvas->recordingContext());
+ if (directContext == nullptr) {
// We're dumping a picture, render a light-blue rectangle instead
// TODO: Draw the WebView text on top? Seemingly complicated as SkPaint doesn't
// seem to have a default typeface that works. We only ever use drawGlyphs, which
@@ -87,7 +88,7 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) {
SkImageInfo surfaceInfo =
canvas->imageInfo().makeWH(clipBounds.width(), clipBounds.height());
tmpSurface =
- SkSurface::MakeRenderTarget(canvas->getGrContext(), SkBudgeted::kYes, surfaceInfo);
+ SkSurface::MakeRenderTarget(directContext, SkBudgeted::kYes, surfaceInfo);
tmpSurface->getCanvas()->clear(SK_ColorTRANSPARENT);
GrGLFramebufferInfo fboInfo;
@@ -141,7 +142,7 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) {
// notify Skia that we just updated the FBO and stencil
const uint32_t grState = kStencil_GrGLBackendState | kRenderTarget_GrGLBackendState;
- canvas->getGrContext()->resetContext(grState);
+ directContext->resetContext(grState);
SkCanvas* tmpCanvas = canvas;
if (tmpSurface) {
@@ -188,7 +189,7 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) {
glClear(GL_STENCIL_BUFFER_BIT);
}
- canvas->getGrContext()->resetContext();
+ directContext->resetContext();
// if there were unclipped save layers involved we draw our offscreen surface to the canvas
if (tmpSurface) {
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 6dd36981e8aa..6e7493cb443d 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -86,7 +86,7 @@ void SkiaPipeline::renderLayers(const LightGeometry& lightGeometry,
}
void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) {
- sk_sp<GrContext> cachedContext;
+ sk_sp<GrDirectContext> cachedContext;
// Render all layers that need to be updated, in order.
for (size_t i = 0; i < layers.entries().size(); i++) {
@@ -142,7 +142,8 @@ void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque)
// cache the current context so that we can defer flushing it until
// either all the layers have been rendered or the context changes
- GrContext* currentContext = layerNode->getLayerSurface()->getCanvas()->getGrContext();
+ GrDirectContext* currentContext =
+ GrAsDirectContext(layerNode->getLayerSurface()->getCanvas()->recordingContext());
if (cachedContext.get() != currentContext) {
if (cachedContext.get()) {
ATRACE_NAME("flush layers (context changed)");
@@ -201,7 +202,7 @@ bool SkiaPipeline::createOrUpdateLayer(RenderNode* node, const DamageAccumulator
}
void SkiaPipeline::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) {
- GrContext* context = thread.getGrContext();
+ GrDirectContext* context = thread.getGrContext();
if (context) {
ATRACE_FORMAT("Bitmap#prepareToDraw %dx%d", bitmap->width(), bitmap->height());
auto image = bitmap->makeImage();
diff --git a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
index 50b45e6eb7ec..6efe1762976b 100644
--- a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
@@ -96,7 +96,7 @@ void VkFunctorDrawable::onDraw(SkCanvas* canvas) {
// "VkFunctorDrawable::onDraw" is not invoked for the most common case, when drawing in a GPU
// canvas.
- if (canvas->getGrContext() == nullptr) {
+ if (canvas->recordingContext() == nullptr) {
// We're dumping a picture, render a light-blue rectangle instead
SkPaint paint;
paint.setColor(0xFF81D4FA);
diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
index 403d9075dbd1..bc8ce428ce2e 100644
--- a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
@@ -67,7 +67,7 @@ void VkInteropFunctorDrawable::vkInvokeFunctor(Functor* functor) {
void VkInteropFunctorDrawable::onDraw(SkCanvas* canvas) {
ATRACE_CALL();
- if (canvas->getGrContext() == nullptr) {
+ if (canvas->recordingContext() == nullptr) {
SkDEBUGF(("Attempting to draw VkInteropFunctor into an unsupported surface"));
return;
}
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 13d544c68e95..04359818e1b7 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;
@@ -484,6 +487,14 @@ void CanvasContext::draw() {
waitOnFences();
+ if (mNativeSurface) {
+ // TODO(b/165985262): measure performance impact
+ if (const auto vsyncId = mCurrentFrameInfo->get(FrameInfoIndex::FrameTimelineVsyncId);
+ vsyncId != UiFrameInfoBuilder::INVALID_VSYNC_ID) {
+ native_window_set_frame_timeline_vsync(mNativeSurface->getNativeWindow(), vsyncId);
+ }
+ }
+
bool requireSwap = false;
int error = OK;
bool didSwap =
@@ -617,8 +628,11 @@ void CanvasContext::prepareAndDraw(RenderNode* node) {
ATRACE_CALL();
nsecs_t vsync = mRenderThread.timeLord().computeFrameTimeNanos();
+ int64_t vsyncId = mRenderThread.timeLord().lastVsyncId();
int64_t frameInfo[UI_THREAD_FRAME_INFO_SIZE];
- UiFrameInfoBuilder(frameInfo).addFlag(FrameInfoFlags::RTAnimation).setVsync(vsync, vsync);
+ UiFrameInfoBuilder(frameInfo)
+ .addFlag(FrameInfoFlags::RTAnimation)
+ .setVsync(vsync, vsync, vsyncId);
TreeInfo info(TreeInfo::MODE_RT_ONLY, *this);
prepareTree(info, frameInfo, systemTime(SYSTEM_TIME_MONOTONIC), node);
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index cba710f01063..cc4eb3285bbe 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -106,7 +106,7 @@ public:
* If Properties::isSkiaEnabled() is true then this will return the Skia
* grContext associated with the current RenderPipeline.
*/
- GrContext* getGrContext() const { return mRenderThread.getGrContext(); }
+ GrDirectContext* getGrContext() const { return mRenderThread.getGrContext(); }
// Won't take effect until next EGLSurface creation
void setSwapBehavior(SwapBehavior swapBehavior);
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 1e593388d063..1ea595d6a30a 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -128,7 +128,9 @@ void DrawFrameTask::run() {
bool DrawFrameTask::syncFrameState(TreeInfo& info) {
ATRACE_CALL();
int64_t vsync = mFrameInfo[static_cast<int>(FrameInfoIndex::Vsync)];
- mRenderThread->timeLord().vsyncReceived(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);
bool canDraw = mContext->makeCurrent();
mContext->unpinImages();
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 4dcbc4458e97..9371656eda7f 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -51,8 +51,10 @@ 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);
rt->mVsyncRequested = false;
- if (rt->timeLord().vsyncReceived(frameTimeNanos) && !rt->mFrameCallbackTaskPending) {
+ if (rt->timeLord().vsyncReceived(frameTimeNanos, frameTimeNanos, vsyncId) &&
+ !rt->mFrameCallbackTaskPending) {
ATRACE_NAME("queue mFrameCallbackTask");
rt->mFrameCallbackTaskPending = true;
nsecs_t runAt = (frameTimeNanos + rt->mDispatchFrameDelay);
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index d7dc00b8a5c1..4fbb07168ac0 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -19,8 +19,8 @@
#include <GrDirectContext.h>
#include <SkBitmap.h>
-#include <apex/choreographer.h>
#include <cutils/compiler.h>
+#include <private/android/choreographer.h>
#include <thread/ThreadBase.h>
#include <utils/Looper.h>
#include <utils/Thread.h>
diff --git a/libs/hwui/renderthread/TimeLord.cpp b/libs/hwui/renderthread/TimeLord.cpp
index 784068f1d877..7dc36c449568 100644
--- a/libs/hwui/renderthread/TimeLord.cpp
+++ b/libs/hwui/renderthread/TimeLord.cpp
@@ -19,9 +19,17 @@ namespace android {
namespace uirenderer {
namespace renderthread {
-TimeLord::TimeLord() : mFrameIntervalNanos(milliseconds_to_nanoseconds(16)), mFrameTimeNanos(0) {}
+TimeLord::TimeLord() : mFrameIntervalNanos(milliseconds_to_nanoseconds(16)),
+ mFrameTimeNanos(0),
+ mFrameIntendedTimeNanos(0),
+ mFrameVsyncId(-1) {}
+
+bool TimeLord::vsyncReceived(nsecs_t vsync, nsecs_t intendedVsync, int64_t vsyncId) {
+ if (intendedVsync > mFrameIntendedTimeNanos) {
+ mFrameIntendedTimeNanos = intendedVsync;
+ mFrameVsyncId = vsyncId;
+ }
-bool TimeLord::vsyncReceived(nsecs_t vsync) {
if (vsync > mFrameTimeNanos) {
mFrameTimeNanos = vsync;
return true;
@@ -36,6 +44,8 @@ nsecs_t TimeLord::computeFrameTimeNanos() {
if (jitterNanos >= mFrameIntervalNanos) {
nsecs_t lastFrameOffset = jitterNanos % mFrameIntervalNanos;
mFrameTimeNanos = now - lastFrameOffset;
+ // mFrameVsyncId is not adjusted here as we still want to send
+ // the vsync id that started this frame to the Surface Composer
}
return mFrameTimeNanos;
}
diff --git a/libs/hwui/renderthread/TimeLord.h b/libs/hwui/renderthread/TimeLord.h
index 68a0f7f971b9..23c1e51c427a 100644
--- a/libs/hwui/renderthread/TimeLord.h
+++ b/libs/hwui/renderthread/TimeLord.h
@@ -32,9 +32,10 @@ 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);
+ bool vsyncReceived(nsecs_t vsync, nsecs_t indendedVsync, int64_t vsyncId);
nsecs_t latestVsync() { return mFrameTimeNanos; }
nsecs_t computeFrameTimeNanos();
+ int64_t lastVsyncId() const { return mFrameVsyncId; }
private:
friend class RenderThread;
@@ -44,6 +45,8 @@ private:
nsecs_t mFrameIntervalNanos;
nsecs_t mFrameTimeNanos;
+ nsecs_t mFrameIntendedTimeNanos;
+ int64_t mFrameVsyncId;
};
} /* namespace renderthread */
diff --git a/libs/hwui/tests/macrobench/TestSceneRunner.cpp b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
index 801cb7d9e8c5..ed89c590be10 100644
--- a/libs/hwui/tests/macrobench/TestSceneRunner.cpp
+++ b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
@@ -145,7 +145,8 @@ void run(const TestScene::Info& info, const TestScene::Options& opts,
for (int i = 0; i < warmupFrameCount; i++) {
testContext.waitForVsync();
nsecs_t vsync = systemTime(SYSTEM_TIME_MONOTONIC);
- UiFrameInfoBuilder(proxy->frameInfo()).setVsync(vsync, vsync);
+ UiFrameInfoBuilder(proxy->frameInfo())
+ .setVsync(vsync, vsync, UiFrameInfoBuilder::INVALID_VSYNC_ID);
proxy->syncAndDrawFrame();
}
@@ -165,7 +166,8 @@ void run(const TestScene::Info& info, const TestScene::Options& opts,
nsecs_t vsync = systemTime(SYSTEM_TIME_MONOTONIC);
{
ATRACE_NAME("UI-Draw Frame");
- UiFrameInfoBuilder(proxy->frameInfo()).setVsync(vsync, vsync);
+ UiFrameInfoBuilder(proxy->frameInfo())
+ .setVsync(vsync, vsync, UiFrameInfoBuilder::INVALID_VSYNC_ID);
scene->doFrame(i);
proxy->syncAndDrawFrame();
}
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index c1a98eabb2b7..31dcd7e65663 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -47,7 +47,7 @@ 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 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);
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index d7ef24170c24..6e597b2c1d63 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;
@@ -783,22 +785,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);
+ }
}
/**
@@ -1024,9 +1029,7 @@ public class LocationManager {
requestLocationUpdates(
provider,
- new LocationRequest.Builder(minTimeMs)
- .setMinUpdateDistanceMeters(minDistanceM)
- .build(),
+ createFromDeprecatedProvider(provider, minTimeMs, minDistanceM, false),
executor,
listener);
}
@@ -1085,10 +1088,7 @@ public class LocationManager {
requestLocationUpdates(
FUSED_PROVIDER,
- new LocationRequest.Builder(minTimeMs)
- .setQuality(criteria)
- .setMinUpdateDistanceMeters(minDistanceM)
- .build(),
+ createFromDeprecatedCriteria(criteria, minTimeMs, minDistanceM, false),
executor,
listener);
}
@@ -1116,9 +1116,7 @@ public class LocationManager {
requestLocationUpdates(
provider,
- new LocationRequest.Builder(minTimeMs)
- .setMinUpdateDistanceMeters(minDistanceM)
- .build(),
+ createFromDeprecatedProvider(provider, minTimeMs, minDistanceM, false),
pendingIntent);
}
@@ -1144,10 +1142,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);
}
@@ -2519,7 +2514,7 @@ public class LocationManager {
}
private static class GetCurrentLocationTransport extends ILocationCallback.Stub implements
- ListenerExecutor {
+ ListenerExecutor, CancellationSignal.OnCancelListener {
private final Executor mExecutor;
@@ -2527,33 +2522,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();
- }
}
}
@@ -2563,7 +2547,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 911b9ab9c973..0521b10a2530 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -475,7 +475,7 @@ public final class LocationRequest implements Parcelable {
/**
* Returns the minimum update interval. If location updates are available faster than the
- * request interval then locations will only be delivered if the minimum update interval has
+ * request interval then locations will only be updated if the minimum update interval has
* expired since the last location update.
*
* <p class=note><strong>Note:</strong> Some allowance for jitter is already built into the
@@ -951,9 +951,9 @@ public final class LocationRequest implements Parcelable {
/**
* Sets an explicit minimum update interval. If location updates are available faster than
- * the request interval then locations will only be delivered if the minimum update interval
- * has expired since the last location update. Defaults to no explicit minimum update
- * interval set, which means the minimum update interval is the same as the interval.
+ * the request interval then an update will only occur if the minimum update interval has
+ * expired since the last location update. Defaults to no explicit minimum update interval
+ * set, which means the minimum update interval is the same as the interval.
*
* <p class=note><strong>Note:</strong> Some allowance for jitter is already built into the
* minimum update interval, so you need not worry about updates blocked simply because they
@@ -972,7 +972,7 @@ public final class LocationRequest implements Parcelable {
/**
* Clears an explicitly set minimum update interval and reverts to an implicit minimum
- * update interval (ie, the minimum update interval is same value as the interval).
+ * update interval (ie, the minimum update interval is the same value as the interval).
*/
public @NonNull Builder clearMinUpdateIntervalMillis() {
mMinUpdateIntervalMillis = IMPLICIT_MIN_UPDATE_INTERVAL;
@@ -980,8 +980,8 @@ public final class LocationRequest implements Parcelable {
}
/**
- * Sets the minimum update distance between delivered locations. If a potential location
- * update is closer to the last delivered location than the minimum update distance, then
+ * Sets the minimum update distance between location updates. If a potential location
+ * update is closer to the last location update than the minimum update distance, then
* the potential location update will not occur. Defaults to 0, which represents no minimum
* update distance.
*/
diff --git a/media/Android.bp b/media/Android.bp
index 0ed10472561d..8895b3a9a2ba 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -23,6 +23,25 @@ aidl_interface {
}
aidl_interface {
+ name: "media_permission-aidl",
+ unstable: true,
+ local_include_dir: "java",
+ srcs: [
+ "java/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",
@@ -61,5 +80,8 @@ aidl_interface {
enabled: false,
},
},
- imports: [ "audio_common-aidl" ],
+ imports: [
+ "audio_common-aidl",
+ "media_permission-aidl",
+ ],
}
diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING
index 6dc0c6f5b676..cf2f0f0da2b7 100644
--- a/media/TEST_MAPPING
+++ b/media/TEST_MAPPING
@@ -4,21 +4,13 @@
"name": "GtsMediaTestCases",
"options" : [
{
- "include-annotation": "android.platform.test.annotations.Presubmit"
+ "include-annotation": "android.platform.test.annotations.Presubmit"
},
{
"include-filter": "com.google.android.media.gts.WidevineGenericOpsTests"
- }
- ]
- },
- {
- "name": "GtsExoPlayerTestCases",
- "options" : [
- {
- "include-annotation": "android.platform.test.annotations.SocPresubmit"
},
{
- "include-filter": "com.google.android.exoplayer.gts.DashTest#testWidevine23FpsH264Fixed"
+ "include-filter": "com.google.android.media.gts.WidevineYouTubePerformanceTests"
}
]
}
diff --git a/media/java/android/media/CamcorderProfile.java b/media/java/android/media/CamcorderProfile.java
index f89893136804..45f1ca044afb 100644
--- a/media/java/android/media/CamcorderProfile.java
+++ b/media/java/android/media/CamcorderProfile.java
@@ -19,6 +19,8 @@ package android.media;
import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.Camera;
import android.hardware.Camera.CameraInfo;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraMetadata;
import android.os.Build;
/**
@@ -418,7 +420,11 @@ public class CamcorderProfile
* resolution and higher audio sampling rate, etc, than those with lower quality
* level.
*
- * @param cameraId the id for the camera
+ * @param cameraId the id for the camera. Integer camera ids parsed from the list received by
+ * invoking {@link CameraManager#getCameraIdList} can be used as long as they
+ * are {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE}
+ * and not
+ * {@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL EXTERNAL}.
* @param quality the target quality level for the camcorder profile.
* @see #QUALITY_LOW
* @see #QUALITY_HIGH
@@ -512,7 +518,11 @@ public class CamcorderProfile
* @see android.hardware.camera2.CameraManager
* @see android.hardware.camera2.CameraCharacteristics
*
- * @param cameraId the id for the camera
+ * @param cameraId the id for the camera. Integer camera ids parsed from the list received by
+ * invoking {@link CameraManager#getCameraIdList} can be used as long as they
+ * are {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE}
+ * and not
+ * {@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL EXTERNAL}.
* @param quality the target quality level for the camcorder profile
*/
public static boolean hasProfile(int cameraId, int quality) {
diff --git a/media/java/android/media/DrmInitData.java b/media/java/android/media/DrmInitData.java
index d803311fdae3..85b4ba54de63 100644
--- a/media/java/android/media/DrmInitData.java
+++ b/media/java/android/media/DrmInitData.java
@@ -87,13 +87,13 @@ public abstract class DrmInitData {
public final byte[] data;
/**
+ * Creates a new instance with the given values.
+ *
* @param uuid The UUID associated with this scheme initialization data.
* @param mimeType The mimeType of the initialization data.
* @param data The initialization data.
- *
- * @hide
*/
- public SchemeInitData(UUID uuid, String mimeType, byte[] data) {
+ public SchemeInitData(@NonNull UUID uuid, @NonNull String mimeType, @NonNull byte[] data) {
this.uuid = uuid;
this.mimeType = mimeType;
this.data = data;
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 57e8e438d592..ddc7db771550 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;
@@ -1529,9 +1530,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 +1539,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 +1549,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 +2202,7 @@ public class ExifInterface {
// Read the thumbnail.
InputStream in = null;
+ FileDescriptor newFileDescriptor = null;
try {
if (mAssetInputStream != null) {
in = mAssetInputStream;
@@ -2211,9 +2215,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 +2238,9 @@ public class ExifInterface {
Log.d(TAG, "Encountered exception while getting thumbnail", e);
} finally {
closeQuietly(in);
+ if (newFileDescriptor != null) {
+ closeFileDescriptor(newFileDescriptor);
+ }
}
return null;
}
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/MediaExtractor.java b/media/java/android/media/MediaExtractor.java
index 6ef871383a58..8f603300dc11 100644
--- a/media/java/android/media/MediaExtractor.java
+++ b/media/java/android/media/MediaExtractor.java
@@ -22,10 +22,6 @@ import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
-import android.media.AudioPresentation;
-import android.media.MediaCodec;
-import android.media.MediaFormat;
-import android.media.MediaHTTPService;
import android.net.Uri;
import android.os.IBinder;
import android.os.IHwBinder;
diff --git a/media/java/android/media/MediaTranscodeManager.java b/media/java/android/media/MediaTranscodeManager.java
index 451677f6a8bc..e09241137541 100644
--- a/media/java/android/media/MediaTranscodeManager.java
+++ b/media/java/android/media/MediaTranscodeManager.java
@@ -274,7 +274,7 @@ public final class MediaTranscodeManager {
private static IMediaTranscodingService getService(boolean retry) {
int retryCount = !retry ? 1 : CONNECT_SERVICE_RETRY_COUNT;
- Log.i(TAG, "get service with rety " + retryCount);
+ Log.i(TAG, "get service with retry " + retryCount);
for (int count = 1; count <= retryCount; count++) {
Log.d(TAG, "Trying to connect to service. Try count: " + count);
IMediaTranscodingService service = IMediaTranscodingService.Stub.asInterface(
@@ -356,7 +356,15 @@ public final class MediaTranscodeManager {
for (TranscodingJob job : retryJobs) {
// Notify the job failure if we fails to connect to the service or fail
// to retry the job.
- if (!haveTranscodingClient || !job.retry()) {
+ if (!haveTranscodingClient) {
+ // TODO(hkuang): Return correct error code to the client.
+ handleTranscodingFailed(job.getJobId(), 0 /*unused */);
+ }
+
+ try {
+ // Do not set hasRetried for retry initiated by MediaTranscodeManager.
+ job.retryInternal(false /*setHasRetried*/);
+ } catch (Exception re) {
// TODO(hkuang): Return correct error code to the client.
handleTranscodingFailed(job.getJobId(), 0 /*unused */);
}
@@ -1010,9 +1018,9 @@ public final class MediaTranscodeManager {
@IntRange(from = 0, to = 100) int progress);
}
- private final ITranscodingClient mJobOwner;
- private final Executor mListenerExecutor;
- private final OnTranscodingFinishedListener mListener;
+ private final MediaTranscodeManager mManager;
+ private Executor mListenerExecutor;
+ private OnTranscodingFinishedListener mListener;
private int mJobId = -1;
// Lock for internal state.
private final Object mLock = new Object();
@@ -1028,20 +1036,26 @@ public final class MediaTranscodeManager {
private @Status int mStatus = STATUS_PENDING;
@GuardedBy("mLock")
private @Result int mResult = RESULT_NONE;
+ @GuardedBy("mLock")
+ private boolean mHasRetried = false;
+ // The original request that associated with this job.
+ private final TranscodingRequest mRequest;
private TranscodingJob(
- @NonNull ITranscodingClient jobOwner,
+ @NonNull MediaTranscodeManager manager,
+ @NonNull TranscodingRequest request,
@NonNull TranscodingJobParcel parcel,
@NonNull @CallbackExecutor Executor executor,
@NonNull OnTranscodingFinishedListener listener) {
- Objects.requireNonNull(jobOwner, "JobOwner must not be null");
- Objects.requireNonNull(parcel, "TranscodingJobParcel must not be null");
+ Objects.requireNonNull(manager, "manager must not be null");
+ Objects.requireNonNull(parcel, "parcel must not be null");
Objects.requireNonNull(executor, "listenerExecutor must not be null");
Objects.requireNonNull(listener, "listener must not be null");
- mJobOwner = jobOwner;
+ mManager = manager;
mJobId = parcel.jobId;
mListenerExecutor = executor;
mListener = listener;
+ mRequest = request;
}
/**
@@ -1085,14 +1099,61 @@ public final class MediaTranscodeManager {
/**
* Resubmit the transcoding job to the service.
+ * Note that only the job that fails or gets cancelled could be retried and each job could
+ * be retried only once. After that, Client need to enqueue a new request if they want to
+ * try again.
*
- * @return true if successfully resubmit the job to the service. False otherwise.
+ * @throws MediaTranscodingException.ServiceNotAvailableException if the service
+ * is temporarily unavailable due to internal service rebooting. Client could retry
+ * again after receiving this exception.
+ * @throws UnsupportedOperationException if the retry could not be fulfilled.
+ * @hide
*/
- public boolean retry() {
+ public void retry() throws MediaTranscodingException.ServiceNotAvailableException {
+ retryInternal(true /*setHasRetried*/);
+ }
+
+ // TODO(hkuang): Add more test for it.
+ private void retryInternal(boolean setHasRetried)
+ throws MediaTranscodingException.ServiceNotAvailableException {
synchronized (mLock) {
- // TODO(hkuang): Implement this.
+ if (mStatus == STATUS_PENDING || mStatus == STATUS_RUNNING) {
+ throw new UnsupportedOperationException(
+ "Failed to retry as job is in processing");
+ }
+
+ if (mHasRetried) {
+ throw new UnsupportedOperationException("Job has been retried already");
+ }
+
+ // Get the client interface.
+ ITranscodingClient client = mManager.getTranscodingClient();
+ if (client == null) {
+ throw new MediaTranscodingException.ServiceNotAvailableException(
+ "Service rebooting. Try again later");
+ }
+
+ synchronized (mManager.mPendingTranscodingJobs) {
+ try {
+ // Submits the request to MediaTranscoding service.
+ TranscodingJobParcel jobParcel = new TranscodingJobParcel();
+ if (!client.submitRequest(mRequest.writeToParcel(), jobParcel)) {
+ mHasRetried = true;
+ throw new UnsupportedOperationException("Failed to enqueue request");
+ }
+
+ // Replace the old job id wit the new one.
+ mJobId = jobParcel.jobId;
+ // Adds the new job back into pending jobs.
+ mManager.mPendingTranscodingJobs.put(mJobId, this);
+ } catch (RemoteException re) {
+ throw new MediaTranscodingException.ServiceNotAvailableException(
+ "Failed to resubmit request to Transcoding service");
+ }
+ mStatus = STATUS_PENDING;
+ mHasRetried = setHasRetried ? true : false;
+ }
}
- return true;
}
/**
@@ -1105,7 +1166,11 @@ public final class MediaTranscodeManager {
// Check if the job is finished already.
if (mStatus != STATUS_FINISHED) {
try {
- mJobOwner.cancelJob(mJobId);
+ ITranscodingClient client = mManager.getTranscodingClient();
+ // The client may be gone.
+ if (client != null) {
+ client.cancelJob(mJobId);
+ }
} catch (RemoteException re) {
//TODO(hkuang): Find out what to do if failing to cancel the job.
Log.e(TAG, "Failed to cancel the job due to exception: " + re);
@@ -1173,6 +1238,12 @@ public final class MediaTranscodeManager {
}
}
+ private ITranscodingClient getTranscodingClient() {
+ synchronized (mLock) {
+ return mTranscodingClient;
+ }
+ }
+
/**
* Enqueues a TranscodingRequest for execution.
* <p> Upon successfully accepting the request, MediaTranscodeManager will return a
@@ -1185,13 +1256,17 @@ public final class MediaTranscodeManager {
* @return A TranscodingJob for this operation.
* @throws FileNotFoundException if the source Uri or destination Uri could not be opened.
* @throws UnsupportedOperationException if the request could not be fulfilled.
+ * @throws MediaTranscodingException.ServiceNotAvailableException if the service
+ * is temporarily unavailable due to internal service rebooting. Client could retry
+ * again after receiving this exception.
*/
@NonNull
public TranscodingJob enqueueRequest(
@NonNull TranscodingRequest transcodingRequest,
@NonNull @CallbackExecutor Executor listenerExecutor,
@NonNull OnTranscodingFinishedListener listener)
- throws FileNotFoundException {
+ throws FileNotFoundException,
+ MediaTranscodingException.ServiceNotAvailableException {
Log.i(TAG, "enqueueRequest called.");
Objects.requireNonNull(transcodingRequest, "transcodingRequest must not be null");
Objects.requireNonNull(listenerExecutor, "listenerExecutor must not be null");
@@ -1208,7 +1283,14 @@ public final class MediaTranscodeManager {
synchronized (mPendingTranscodingJobs) {
synchronized (mLock) {
if (mTranscodingClient == null) {
- // TODO(hkuang): Handle the case if client is temporarily unavailable.
+ // Try to register with the service again.
+ IMediaTranscodingService service = getService(false /*retry*/);
+ mTranscodingClient = registerClient(service);
+ // If still fails, throws an exception to tell client to try later.
+ if (mTranscodingClient == null) {
+ throw new MediaTranscodingException.ServiceNotAvailableException(
+ "Service rebooting. Try again later");
+ }
}
if (!mTranscodingClient.submitRequest(requestParcel, jobParcel)) {
@@ -1218,7 +1300,8 @@ public final class MediaTranscodeManager {
// Wraps the TranscodingJobParcel into a TranscodingJob and returns it to client for
// tracking.
- TranscodingJob job = new TranscodingJob(mTranscodingClient, jobParcel,
+ TranscodingJob job = new TranscodingJob(this, transcodingRequest,
+ jobParcel,
listenerExecutor,
listener);
diff --git a/media/java/android/media/MediaTranscodingException.java b/media/java/android/media/MediaTranscodingException.java
new file mode 100644
index 000000000000..50cc9c4bae24
--- /dev/null
+++ b/media/java/android/media/MediaTranscodingException.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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;
+
+/**
+ * Base class for MediaTranscoding exceptions
+ */
+public class MediaTranscodingException extends Exception {
+ private MediaTranscodingException(String detailMessage) {
+ super(detailMessage);
+ }
+
+ /**
+ * Exception thrown when the service is rebooting and MediaTranscodeManager is temporarily
+ * unavailable for accepting new request. It's likely that retrying will be successful.
+ */
+ public static final class ServiceNotAvailableException extends
+ MediaTranscodingException {
+ /** @hide */
+ public ServiceNotAvailableException(String detailMessage) {
+ super(detailMessage);
+ }
+ }
+}
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/permission/ClearCallingIdentityContext.java b/media/java/android/media/permission/ClearCallingIdentityContext.java
new file mode 100644
index 000000000000..364a2e800afe
--- /dev/null
+++ b/media/java/android/media/permission/ClearCallingIdentityContext.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.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();
+ }
+
+ private ClearCallingIdentityContext() {
+ mRestoreKey = Binder.clearCallingIdentity();
+ }
+
+ @Override
+ 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/Identity.aidl b/media/java/android/media/permission/Identity.aidl
new file mode 100644
index 000000000000..361497d59ea9
--- /dev/null
+++ b/media/java/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/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/tests/utils/testutils/java/android/view/test/InsetsModeSession.java b/media/java/android/media/permission/SafeCloseable.java
index e05fdce0ca0c..8ec15b18bd37 100644
--- a/tests/utils/testutils/java/android/view/test/InsetsModeSession.java
+++ b/media/java/android/media/permission/SafeCloseable.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,24 +14,14 @@
* limitations under the License.
*/
-package android.view.test;
-
-import android.view.ViewRootImpl;
+package android.media.permission;
/**
- * Session to set insets mode for {@link ViewRootImpl#sNewInsetsMode}.
+ * An {@link AutoCloseable} that doesn't throw on {@link #close()}.
+ *
+ * @hide
*/
-public class InsetsModeSession implements AutoCloseable {
-
- private int mOldMode;
-
- public InsetsModeSession(int flag) {
- mOldMode = ViewRootImpl.sNewInsetsMode;
- ViewRootImpl.sNewInsetsMode = flag;
- }
-
+public interface SafeCloseable extends AutoCloseable {
@Override
- public void close() {
- ViewRootImpl.sNewInsetsMode = mOldMode;
- }
+ void close();
}
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 9494295e4bd9..3acb9516054e 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) {
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
index 06c39071cdf5..d1126b9006e0 100644
--- a/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl
+++ b/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl
@@ -15,6 +15,7 @@
*/
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;
@@ -30,13 +31,59 @@ import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
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[] listModules();
+ 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 attach(int handle, ISoundTriggerCallback callback);
-} \ No newline at end of file
+ ISoundTriggerModule attachAsMiddleman(int handle,
+ in Identity middlemanIdentity,
+ in Identity originatorIdentity,
+ ISoundTriggerCallback callback);
+}
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 5db672993df1..5daf8b0f88f8 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -171,6 +171,12 @@ static int IP_V6_LENGTH = 16;
void DestroyCallback(const C2Buffer * /* buf */, void *arg) {
android::sp<android::MediaEvent> event = (android::MediaEvent *)arg;
+ if (event->mLinearBlockObj != NULL) {
+ JNIEnv *env = android::AndroidRuntime::getJNIEnv();
+ env->DeleteWeakGlobalRef(event->mLinearBlockObj);
+ event->mLinearBlockObj = NULL;
+ }
+
event->mAvHandleRefCnt--;
event->finalize();
}
@@ -182,6 +188,12 @@ LnbCallback::LnbCallback(jobject lnbObj, LnbId id) : mId(id) {
mLnb = env->NewWeakGlobalRef(lnbObj);
}
+LnbCallback::~LnbCallback() {
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ env->DeleteWeakGlobalRef(mLnb);
+ mLnb = NULL;
+}
+
Return<void> LnbCallback::onEvent(LnbEventType lnbEventType) {
ALOGD("LnbCallback::onEvent, type=%d", lnbEventType);
JNIEnv *env = AndroidRuntime::getJNIEnv();
@@ -305,6 +317,7 @@ MediaEvent::MediaEvent(sp<IFilter> iFilter, hidl_handle avHandle,
JNIEnv *env = AndroidRuntime::getJNIEnv();
mMediaEventObj = env->NewWeakGlobalRef(obj);
mAvHandle = native_handle_clone(avHandle.getNativeHandle());
+ mLinearBlockObj = NULL;
}
MediaEvent::~MediaEvent() {
@@ -367,7 +380,7 @@ jobject MediaEvent::getLinearBlock() {
true);
mLinearBlockObj = env->NewWeakGlobalRef(linearBlock);
mAvHandleRefCnt++;
- return mLinearBlockObj;
+ return linearBlock;
} else {
native_handle_close(const_cast<native_handle_t*>(
reinterpret_cast<const native_handle_t*>(mIonHandle)));
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index fd2995917475..c4deeaf887bb 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -73,6 +73,7 @@ namespace android {
struct LnbCallback : public ILnbCallback {
LnbCallback(jweak tunerObj, LnbId id);
+ ~LnbCallback();
virtual Return<void> onEvent(LnbEventType lnbEventType);
virtual Return<void> onDiseqcMessage(const hidl_vec<uint8_t>& diseqcMessage);
jweak mLnb;
diff --git a/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerDiedTest.java b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerDiedTest.java
index f00c14d79bec..6b5abccccb8b 100644
--- a/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerDiedTest.java
+++ b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerDiedTest.java
@@ -22,6 +22,7 @@ import android.media.MediaFormat;
import android.media.MediaTranscodeManager;
import android.media.MediaTranscodeManager.TranscodingJob;
import android.media.MediaTranscodeManager.TranscodingRequest;
+import android.media.MediaTranscodingException;
import android.net.Uri;
import android.os.Bundle;
import android.os.FileUtils;
@@ -250,6 +251,27 @@ public class MediaTranscodeManagerDiedTest
TRANSCODE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
assertTrue("Invalid job status", job.getStatus() == TranscodingJob.STATUS_FINISHED);
assertTrue("Invalid job result", job.getResult()== TranscodingJob.RESULT_ERROR);
+
+
+ boolean retryJob = false;
+ // Wait till service is available again.
+ Log.d(TAG, "Retry the failed transcoding job");
+ while (!retryJob) {
+ try {
+ job.retry();
+ // Break out when job retry succeeds.
+ break;
+ } catch (MediaTranscodingException.ServiceNotAvailableException ex) {
+ // Sleep for 10 milliseconds to wait.
+ Thread.sleep(10);
+ }
+ }
+
+ finishedOnTime = transcodeCompleteSemaphore.tryAcquire(
+ TRANSCODE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ // Check the make sure job is successfully finished after retry.
+ assertTrue("Invalid job status", job.getStatus() == TranscodingJob.STATUS_FINISHED);
+ assertTrue("Invalid job result", job.getResult() == TranscodingJob.RESULT_SUCCESS);
}
}
diff --git a/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodingBenchmark.java b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodingBenchmark.java
index 73e1d98cb2e2..10262d98d617 100644
--- a/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodingBenchmark.java
+++ b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodingBenchmark.java
@@ -22,6 +22,7 @@ import android.media.MediaFormat;
import android.media.MediaTranscodeManager;
import android.media.MediaTranscodeManager.TranscodingJob;
import android.media.MediaTranscodeManager.TranscodingRequest;
+import android.media.MediaTranscodingException;
import android.net.Uri;
import android.test.ActivityInstrumentationTestCase2;
import android.util.Log;
@@ -101,7 +102,8 @@ public class MediaTranscodingBenchmark
* Transcode the sourceFileName to destinationFileName with LOOP_COUNT.
*/
private void transcode(final String sourceFileName, final String destinationFileName)
- throws IOException, InterruptedException {
+ throws IOException, InterruptedException,
+ MediaTranscodingException.ServiceNotAvailableException {
AtomicLong totalTimeMs = new AtomicLong();
AtomicLong transcodingTime = new AtomicLong();
Uri srcUri = getUri(sourceFileName);
diff --git a/non-updatable-api/current.txt b/non-updatable-api/current.txt
index 8a2976fb9e85..80300611c7f2 100644
--- a/non-updatable-api/current.txt
+++ b/non-updatable-api/current.txt
@@ -11359,6 +11359,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
@@ -11451,6 +11452,7 @@ package android.content.pm {
method public int describeContents();
method public int getKind();
method @Nullable public java.security.cert.Certificate getSourceCertificate() throws java.security.cert.CertificateException;
+ method @Nullable public String getSourcePackageName();
method @Nullable public String getSplitName();
method @NonNull public byte[] getValue();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -11559,6 +11561,7 @@ 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 @NonNull public byte[] getValue();
@@ -11566,9 +11569,9 @@ package android.content.pm {
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 @Deprecated 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 @Deprecated 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
}
@@ -11864,7 +11867,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);
@@ -12061,6 +12064,7 @@ package android.content.pm {
method public boolean getSyntheticAppDetailsActivityEnabled(@NonNull String);
method @NonNull public abstract android.content.pm.FeatureInfo[] getSystemAvailableFeatures();
method @Nullable public abstract String[] getSystemSharedLibraryNames();
+ method @IntRange(from=0) public int getTargetSdkVersion(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method @Nullable public abstract CharSequence getText(@NonNull String, @StringRes int, @Nullable android.content.pm.ApplicationInfo);
method @NonNull public abstract android.graphics.drawable.Drawable getUserBadgedDrawableForDensity(@NonNull android.graphics.drawable.Drawable, @NonNull android.os.UserHandle, @Nullable android.graphics.Rect, int);
method @NonNull public abstract android.graphics.drawable.Drawable getUserBadgedIcon(@NonNull android.graphics.drawable.Drawable, @NonNull android.os.UserHandle);
@@ -12679,6 +12683,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
@@ -12745,6 +12752,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;
@@ -24868,6 +24876,7 @@ package android.media {
}
public static final class DrmInitData.SchemeInitData {
+ ctor public DrmInitData.SchemeInitData(@NonNull java.util.UUID, @NonNull String, @NonNull byte[]);
field @NonNull public static final java.util.UUID UUID_NIL;
field public final byte[] data;
field public final String mimeType;
@@ -27138,6 +27147,12 @@ package android.media {
field public static final android.media.MediaTimestamp TIMESTAMP_UNKNOWN;
}
+ public class MediaTranscodingException extends java.lang.Exception {
+ }
+
+ public static final class MediaTranscodingException.ServiceNotAvailableException extends android.media.MediaTranscodingException {
+ }
+
public interface MicrophoneDirection {
method public boolean setPreferredMicrophoneDirection(int);
method public boolean setPreferredMicrophoneFieldDimension(@FloatRange(from=-1.0, to=1.0) float);
@@ -42422,54 +42437,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);
@@ -44903,6 +44872,7 @@ package android.telephony {
field public static final String KEY_APN_EXPAND_BOOL = "apn_expand_bool";
field public static final String KEY_APN_SETTINGS_DEFAULT_APN_TYPES_STRING_ARRAY = "apn_settings_default_apn_types_string_array";
field public static final String KEY_AUTO_RETRY_ENABLED_BOOL = "auto_retry_enabled_bool";
+ field public static final String KEY_CALL_BARRING_DEFAULT_SERVICE_CLASS_INT = "call_barring_default_service_class_int";
field public static final String KEY_CALL_BARRING_SUPPORTS_DEACTIVATE_ALL_BOOL = "call_barring_supports_deactivate_all_bool";
field public static final String KEY_CALL_BARRING_SUPPORTS_PASSWORD_CHANGE_BOOL = "call_barring_supports_password_change_bool";
field public static final String KEY_CALL_BARRING_VISIBILITY_BOOL = "call_barring_visibility_bool";
@@ -45130,6 +45100,8 @@ package android.telephony {
field public static final String KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING = "wfc_emergency_address_carrier_app_string";
field public static final String KEY_WORLD_MODE_ENABLED_BOOL = "world_mode_enabled_bool";
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
}
public static final class CarrierConfigManager.Apn {
diff --git a/non-updatable-api/module-lib-current.txt b/non-updatable-api/module-lib-current.txt
index 63b98cb5bd6a..85136df0d1ad 100644
--- a/non-updatable-api/module-lib-current.txt
+++ b/non-updatable-api/module-lib-current.txt
@@ -10,6 +10,10 @@ package android.app {
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);
+ }
+
}
package android.content.rollback {
@@ -62,6 +66,7 @@ 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 void dispatchVolumeKeyEventAsSystemService(@NonNull android.view.KeyEvent, int);
@@ -104,6 +109,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 38f745e95d22..10fe058bb76f 100644
--- a/non-updatable-api/system-current.txt
+++ b/non-updatable-api/system-current.txt
@@ -1532,12 +1532,16 @@ package android.bluetooth {
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void setBluetoothTethering(boolean);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED";
+ field public static final String ACTION_TETHERING_STATE_CHANGED = "android.bluetooth.action.TETHERING_STATE_CHANGED";
field public static final String EXTRA_LOCAL_ROLE = "android.bluetooth.pan.extra.LOCAL_ROLE";
+ field public static final String EXTRA_TETHERING_STATE = "android.bluetooth.extra.TETHERING_STATE";
field public static final int LOCAL_NAP_ROLE = 1; // 0x1
field public static final int LOCAL_PANU_ROLE = 2; // 0x2
field public static final int PAN_ROLE_NONE = 0; // 0x0
field public static final int REMOTE_NAP_ROLE = 1; // 0x1
field public static final int REMOTE_PANU_ROLE = 2; // 0x2
+ field public static final int TETHERING_STATE_OFF = 1; // 0x1
+ field public static final int TETHERING_STATE_ON = 2; // 0x2
}
public class BluetoothPbap implements android.bluetooth.BluetoothProfile {
@@ -4303,7 +4307,7 @@ package android.media {
}
public final class MediaTranscodeManager {
- method @NonNull public android.media.MediaTranscodeManager.TranscodingJob enqueueRequest(@NonNull android.media.MediaTranscodeManager.TranscodingRequest, @NonNull java.util.concurrent.Executor, @NonNull android.media.MediaTranscodeManager.OnTranscodingFinishedListener) throws java.io.FileNotFoundException;
+ method @NonNull public android.media.MediaTranscodeManager.TranscodingJob enqueueRequest(@NonNull android.media.MediaTranscodeManager.TranscodingRequest, @NonNull java.util.concurrent.Executor, @NonNull android.media.MediaTranscodeManager.OnTranscodingFinishedListener) throws java.io.FileNotFoundException, android.media.MediaTranscodingException.ServiceNotAvailableException;
field public static final int PRIORITY_REALTIME = 1; // 0x1
field public static final int TRANSCODING_TYPE_VIDEO = 1; // 0x1
}
@@ -4318,7 +4322,6 @@ package android.media {
method @IntRange(from=0, to=100) public int getProgress();
method public int getResult();
method public int getStatus();
- method public boolean retry();
method public void setOnProgressUpdateListener(@NonNull java.util.concurrent.Executor, @Nullable android.media.MediaTranscodeManager.TranscodingJob.OnProgressUpdateListener);
method public void setOnProgressUpdateListener(int, @NonNull java.util.concurrent.Executor, @Nullable android.media.MediaTranscodeManager.TranscodingJob.OnProgressUpdateListener);
field public static final int RESULT_CANCELED = 4; // 0x4
@@ -8238,6 +8241,7 @@ package android.provider {
field public static final String APN_SET_ID = "apn_set_id";
field public static final int CARRIER_EDITED = 4; // 0x4
field public static final String EDITED_STATUS = "edited";
+ field public static final int MATCH_ALL_APN_SET_ID = -1; // 0xffffffff
field public static final String MAX_CONNECTIONS = "max_conns";
field public static final String MODEM_PERSIST = "modem_cognitive";
field public static final String MTU = "mtu";
@@ -9131,7 +9135,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();
}
@@ -9522,6 +9572,26 @@ 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 ERROR_FDN_CHECK_FAILURE = 2; // 0x2
+ field public static final int ERROR_NOT_SUPPORTED = 3; // 0x3
+ field public static final int ERROR_UNKNOWN = 1; // 0x1
+ 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
+ field public static final int SUCCESS = 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);
@@ -9795,7 +9865,8 @@ package android.telephony {
method public void onCallAttributesChanged(@NonNull android.telephony.CallAttributes);
method @Deprecated public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber);
method public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber, int);
- method public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber);
+ method @Deprecated public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber);
+ method public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber, int);
method public void onPhysicalChannelConfigurationChanged(@NonNull java.util.List<android.telephony.PhysicalChannelConfig>);
method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onPreciseCallStateChanged(@NonNull android.telephony.PreciseCallState);
method public void onRadioPowerStateChanged(int);
@@ -10182,6 +10253,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);
@@ -10258,6 +10331,8 @@ 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 setCallWaitingStatus(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);
@@ -10295,6 +10370,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
@@ -10349,6 +10428,11 @@ 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);
+ }
+
public final class UiccAccessRule implements android.os.Parcelable {
ctor public UiccAccessRule(byte[], @Nullable String, long);
method public int describeContents();
diff --git a/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml
index 8b235e6f246e..d57875f8daba 100644
--- a/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml
+++ b/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml
@@ -53,7 +53,7 @@
android:textColor="?attr/wallpaperTextColor"
android:textAppearance="?android:attr/textAppearanceMedium"
android:imeOptions="flagForceAscii|actionDone"
- android:maxLength="@integer/password_text_view_scale"
+ android:maxLength="@integer/password_max_length"
/>
<TextView
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
index 3af7507ae2b4..c190ae54b1cf 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
@@ -124,7 +124,7 @@ public class CarKeyguardViewController extends OverlayViewController implements
}
@Override
- protected boolean shouldShowNavigationBar() {
+ protected boolean shouldShowNavigationBarInsets() {
return true;
}
@@ -157,7 +157,6 @@ public class CarKeyguardViewController extends OverlayViewController implements
mKeyguardStateController.notifyKeyguardState(mShowing, /* occluded= */ false);
mCarNavigationBarController.showAllKeyguardButtons(/* isSetUp= */ true);
start();
- getOverlayViewGlobalStateController().setWindowFocusable(/* focusable= */ true);
reset(/* hideBouncerWhenShowing= */ false);
notifyKeyguardUpdateMonitor();
}
@@ -172,7 +171,6 @@ public class CarKeyguardViewController extends OverlayViewController implements
mBouncer.hide(/* destroyView= */ true);
mCarNavigationBarController.hideAllKeyguardButtons(/* isSetUp= */ true);
stop();
- getOverlayViewGlobalStateController().setWindowFocusable(/* focusable= */ false);
mKeyguardStateController.notifyKeyguardDoneFading();
mHandler.post(mViewMediatorCallback::keyguardGone);
notifyKeyguardUpdateMonitor();
@@ -217,7 +215,6 @@ public class CarKeyguardViewController extends OverlayViewController implements
public void onCancelClicked() {
if (mBouncer == null) return;
- getOverlayViewGlobalStateController().setWindowFocusable(/* focusable= */ false);
getOverlayViewGlobalStateController().setWindowNeedsInput(/* needsInput= */ false);
mBouncer.hide(/* destroyView= */ true);
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 8d91e7eae91a..c7155f4b21d2 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
@@ -193,12 +193,12 @@ public class NotificationPanelViewController extends OverlayPanelViewController
}
@Override
- protected boolean shouldShowNavigationBar() {
+ protected boolean shouldShowNavigationBarInsets() {
return true;
}
@Override
- protected boolean shouldShowStatusBar() {
+ protected boolean shouldShowStatusBarInsets() {
return true;
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java
index 66bfb2d316ae..3a7fac9c0e79 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java
@@ -78,6 +78,11 @@ public class FullScreenUserSwitcherViewController extends OverlayViewController
}
@Override
+ protected boolean shouldFocusWindow() {
+ return false;
+ }
+
+ @Override
protected void showInternal() {
getLayout().setVisibility(View.VISIBLE);
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java
index d928ad0f42c9..44cb5cf9cb54 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java
@@ -238,7 +238,6 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
}
onAnimateCollapsePanel();
- getOverlayViewGlobalStateController().setWindowFocusable(false);
animatePanel(mClosingVelocity, /* isClosing= */ true);
}
@@ -415,7 +414,6 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
getOverlayViewGlobalStateController().hideView(/* panelViewController= */ this);
}
getLayout().setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
- getOverlayViewGlobalStateController().setWindowFocusable(visible);
}
/* ***************************************************************************************** *
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 53deb9d9dc5d..8adc1adcc41c 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java
@@ -136,16 +136,18 @@ public class OverlayViewController {
}
/**
- * Returns {@code true} if navigation bar should be displayed over this view.
+ * Returns {@code true} if navigation bar insets should be displayed over this view. Has no
+ * effect if {@link #shouldFocusWindow} returns {@code false}.
*/
- protected boolean shouldShowNavigationBar() {
+ protected boolean shouldShowNavigationBarInsets() {
return false;
}
/**
- * Returns {@code true} if status bar should be displayed over this view.
+ * Returns {@code true} if status bar insets should be displayed over this view. Has no
+ * effect if {@link #shouldFocusWindow} returns {@code false}.
*/
- protected boolean shouldShowStatusBar() {
+ protected boolean shouldShowStatusBarInsets() {
return false;
}
@@ -157,6 +159,15 @@ public class OverlayViewController {
}
/**
+ * Returns {@code true} if the window should be focued when this view is visible. Note that
+ * returning {@code false} here means that {@link #shouldShowStatusBarInsets} and
+ * {@link #shouldShowNavigationBarInsets} will have no effect.
+ */
+ protected boolean shouldFocusWindow() {
+ return true;
+ }
+
+ /**
* 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 22b6455dbe2b..c13e486f1c0e 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java
@@ -18,7 +18,6 @@ package com.android.systemui.car.window;
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.statusBars;
-import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import android.annotation.Nullable;
import android.util.Log;
@@ -119,6 +118,7 @@ public class OverlayViewGlobalStateController {
updateInternalsWhenShowingView(viewController);
refreshInsetTypesToFit();
+ refreshWindowFocus();
refreshNavigationBarVisibility();
refreshStatusBarVisibility();
@@ -191,6 +191,7 @@ public class OverlayViewGlobalStateController {
mZOrderVisibleSortedMap.remove(mZOrderMap.get(viewController));
refreshHighestZOrderWhenHidingView(viewController);
refreshInsetTypesToFit();
+ refreshWindowFocus();
refreshNavigationBarVisibility();
refreshStatusBarVisibility();
@@ -215,23 +216,37 @@ public class OverlayViewGlobalStateController {
}
private void refreshNavigationBarVisibility() {
- mWindowInsetsController.setSystemBarsBehavior(BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
- if (mZOrderVisibleSortedMap.isEmpty() || mHighestZOrder.shouldShowNavigationBar()) {
+ if (mZOrderVisibleSortedMap.isEmpty()) {
mWindowInsetsController.show(navigationBars());
- } else {
+ return;
+ }
+
+ // Do not hide navigation bar insets if the window is not focusable.
+ if (mHighestZOrder.shouldFocusWindow() && !mHighestZOrder.shouldShowNavigationBarInsets()) {
mWindowInsetsController.hide(navigationBars());
+ } else {
+ mWindowInsetsController.show(navigationBars());
}
}
private void refreshStatusBarVisibility() {
- mWindowInsetsController.setSystemBarsBehavior(BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
- if (mZOrderVisibleSortedMap.isEmpty() || mHighestZOrder.shouldShowStatusBar()) {
+ if (mZOrderVisibleSortedMap.isEmpty()) {
mWindowInsetsController.show(statusBars());
- } else {
+ return;
+ }
+
+ // Do not hide status bar insets if the window is not focusable.
+ if (mHighestZOrder.shouldFocusWindow() && !mHighestZOrder.shouldShowStatusBarInsets()) {
mWindowInsetsController.hide(statusBars());
+ } else {
+ mWindowInsetsController.show(statusBars());
}
}
+ private void refreshWindowFocus() {
+ setWindowFocusable(mHighestZOrder == null ? false : mHighestZOrder.shouldFocusWindow());
+ }
+
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 81b1bf930e7e..887329359b05 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowController.java
@@ -16,6 +16,7 @@
package com.android.systemui.car.window;
+import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import android.content.Context;
@@ -104,6 +105,7 @@ public class SystemUIOverlayWindowController implements
mLp.setTitle("SystemUIOverlayWindow");
mLp.packageName = mContext.getPackageName();
mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ mLp.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
mWindowManager.addView(mBaseLayout, mLp);
mLpChanged.copyFrom(mLp);
@@ -160,6 +162,7 @@ public class SystemUIOverlayWindowController implements
private void updateWindow() {
if (mLp != null && mLp.copyFrom(mLpChanged) != 0) {
if (isAttached()) {
+ mLp.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
mWindowManager.updateViewLayout(mBaseLayout, mLp);
}
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java b/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java
index c7354ed5b459..b113d29f00e6 100644
--- a/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java
@@ -36,6 +36,8 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.TransactionPool;
+import java.util.Objects;
+
/**
* Controller that maps between displays and {@link IDisplayWindowInsetsController} in order to
* give system bar control to SystemUI.
@@ -47,6 +49,7 @@ public class DisplaySystemBarsController extends DisplayImeController {
private static final String TAG = "DisplaySystemBarsController";
private final Context mContext;
+ private final DisplayController mDisplayController;
private SparseArray<PerDisplay> mPerDisplaySparseArray;
public DisplaySystemBarsController(
@@ -57,6 +60,7 @@ public class DisplaySystemBarsController extends DisplayImeController {
TransactionPool transactionPool) {
super(wmService, displayController, mainHandler, transactionPool);
mContext = context;
+ mDisplayController = displayController;
}
@Override
@@ -92,7 +96,7 @@ public class DisplaySystemBarsController extends DisplayImeController {
}
@VisibleForTesting
- class PerDisplay extends IDisplayWindowInsetsController.Stub {
+ class PerDisplay extends DisplayImeController.PerDisplay {
int mDisplayId;
InsetsController mInsetsController;
@@ -100,6 +104,7 @@ public class DisplaySystemBarsController extends DisplayImeController {
String mPackageName;
PerDisplay(int displayId) {
+ super(displayId, mDisplayController.getDisplayLayout(displayId).rotation());
mDisplayId = displayId;
mInsetsController = new InsetsController(
new DisplaySystemBarsInsetsControllerHost(mHandler, this));
@@ -107,6 +112,7 @@ public class DisplaySystemBarsController extends DisplayImeController {
@Override
public void insetsChanged(InsetsState insetsState) {
+ super.insetsChanged(insetsState);
if (mInsetsState.equals(insetsState)) {
return;
}
@@ -120,24 +126,33 @@ public class DisplaySystemBarsController extends DisplayImeController {
@Override
public void insetsControlChanged(InsetsState insetsState,
InsetsSourceControl[] activeControls) {
+ super.insetsControlChanged(insetsState, activeControls);
mInsetsController.onControlsChanged(activeControls);
}
@Override
public void hideInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme) {
- mInsetsController.hide(types);
+ if ((types & WindowInsets.Type.ime()) == 0) {
+ mInsetsController.hide(types);
+ } else {
+ super.hideInsets(types, fromIme);
+ }
+
}
@Override
public void showInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme) {
- mInsetsController.show(types);
+ if ((types & WindowInsets.Type.ime()) == 0) {
+ mInsetsController.show(types);
+ } else {
+ super.showInsets(types, fromIme);
+ }
+
}
@Override
public void topFocusedWindowChanged(String packageName) {
- // If both package names are null or both package names are equal, return.
- if (mPackageName == packageName
- || (mPackageName != null && mPackageName.equals(packageName))) {
+ if (Objects.equals(mPackageName, packageName)) {
return;
}
mPackageName = packageName;
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayPanelViewControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayPanelViewControllerTest.java
index bed803eedc22..f5f3ea3030da 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayPanelViewControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayPanelViewControllerTest.java
@@ -224,18 +224,6 @@ public class OverlayPanelViewControllerTest extends SysuiTestCase {
}
@Test
- public void animateCollapsePanel_removesWindowFocus() {
- mOverlayPanelViewController.inflate(mBaseLayout);
- mOverlayPanelViewController.setShouldAnimateCollapsePanel(true);
- mOverlayPanelViewController.setPanelExpanded(true);
- mOverlayPanelViewController.setPanelVisible(true);
-
- mOverlayPanelViewController.animateCollapsePanel();
-
- verify(mOverlayViewGlobalStateController).setWindowFocusable(false);
- }
-
- @Test
public void animateExpandPanel_shouldNotAnimateExpandPanel_doesNotExpand() {
mOverlayPanelViewController.inflate(mBaseLayout);
mOverlayPanelViewController.setShouldAnimateExpandPanel(false);
@@ -365,14 +353,6 @@ public class OverlayPanelViewControllerTest extends SysuiTestCase {
}
@Test
- public void setPanelVisible_setTrue_setWindowFocusable() {
- mOverlayPanelViewController.inflate(mBaseLayout);
- mOverlayPanelViewController.setPanelVisible(true);
-
- verify(mOverlayViewGlobalStateController).setWindowFocusable(true);
- }
-
- @Test
public void setPanelVisible_setFalse_windowVisible_setsWindowNotVisible() {
mOverlayPanelViewController.inflate(mBaseLayout);
when(mOverlayViewGlobalStateController.isWindowVisible()).thenReturn(true);
@@ -404,15 +384,6 @@ public class OverlayPanelViewControllerTest extends SysuiTestCase {
}
@Test
- public void setPanelVisible_setFalse_setWindowNotFocusable() {
- mOverlayPanelViewController.inflate(mBaseLayout);
-
- mOverlayPanelViewController.setPanelVisible(false);
-
- verify(mOverlayViewGlobalStateController).setWindowFocusable(false);
- }
-
- @Test
public void dragOpenTouchListener_isNotInflated_inflatesView() {
when(mCarDeviceProvisionedController.isCurrentUserFullySetup()).thenReturn(true);
assertThat(mOverlayPanelViewController.isInflated()).isFalse();
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java
index ff286650ea50..294aa0d3cf9b 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java
@@ -108,9 +108,54 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
}
@Test
+ public void showView_nothingVisible_windowNotFocusable_shouldShowNavBar_navBarsVisible() {
+ setupOverlayViewController1();
+ when(mOverlayViewController1.shouldFocusWindow()).thenReturn(false);
+ when(mOverlayViewController1.shouldShowNavigationBarInsets()).thenReturn(true);
+
+ mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
+
+ verify(mWindowInsetsController).show(navigationBars());
+ }
+
+ @Test
+ public void showView_nothingVisible_windowNotFocusable_shouldHideNavBar_notHidden() {
+ setupOverlayViewController1();
+ when(mOverlayViewController1.shouldFocusWindow()).thenReturn(false);
+ when(mOverlayViewController1.shouldShowNavigationBarInsets()).thenReturn(false);
+
+ mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
+
+ verify(mWindowInsetsController, never()).hide(navigationBars());
+ }
+
+ @Test
+ public void showView_nothingVisible_windowNotFocusable_shouldShowStatusBar_statusBarsVisible() {
+ setupOverlayViewController1();
+ when(mOverlayViewController1.shouldFocusWindow()).thenReturn(false);
+ when(mOverlayViewController1.shouldShowStatusBarInsets()).thenReturn(true);
+
+ mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
+
+ verify(mWindowInsetsController).show(statusBars());
+ }
+
+ @Test
+ public void showView_nothingVisible_windowNotFocusable_shouldHideStatusBar_notHidden() {
+ setupOverlayViewController1();
+ when(mOverlayViewController1.shouldFocusWindow()).thenReturn(false);
+ when(mOverlayViewController1.shouldShowStatusBarInsets()).thenReturn(false);
+
+ mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
+
+ verify(mWindowInsetsController, never()).hide(statusBars());
+ }
+
+ @Test
public void showView_nothingAlreadyShown_shouldShowNavBarFalse_navigationBarsHidden() {
setupOverlayViewController1();
- when(mOverlayViewController1.shouldShowNavigationBar()).thenReturn(false);
+ when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+ when(mOverlayViewController1.shouldShowNavigationBarInsets()).thenReturn(false);
mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
@@ -120,7 +165,8 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
@Test
public void showView_nothingAlreadyShown_shouldShowNavBarTrue_navigationBarsShown() {
setupOverlayViewController1();
- when(mOverlayViewController1.shouldShowNavigationBar()).thenReturn(true);
+ when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+ when(mOverlayViewController1.shouldShowNavigationBarInsets()).thenReturn(true);
mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
@@ -130,7 +176,8 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
@Test
public void showView_nothingAlreadyShown_shouldShowStatusBarFalse_statusBarsHidden() {
setupOverlayViewController1();
- when(mOverlayViewController1.shouldShowStatusBar()).thenReturn(false);
+ when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+ when(mOverlayViewController1.shouldShowStatusBarInsets()).thenReturn(false);
mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
@@ -140,7 +187,8 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
@Test
public void showView_nothingAlreadyShown_shouldShowStatusBarTrue_statusBarsShown() {
setupOverlayViewController1();
- when(mOverlayViewController1.shouldShowStatusBar()).thenReturn(true);
+ when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+ when(mOverlayViewController1.shouldShowStatusBarInsets()).thenReturn(true);
mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
@@ -201,9 +249,11 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
@Test
public void showView_newHighestZOrder_shouldShowNavBarFalse_navigationBarsHidden() {
setupOverlayViewController1();
- setOverlayViewControllerAsShowing(mOverlayViewController1);
setupOverlayViewController2();
- when(mOverlayViewController2.shouldShowNavigationBar()).thenReturn(false);
+ when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+ when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
+ when(mOverlayViewController2.shouldShowNavigationBarInsets()).thenReturn(false);
reset(mWindowInsetsController);
mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable);
@@ -214,9 +264,11 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
@Test
public void showView_newHighestZOrder_shouldShowNavBarTrue_navigationBarsShown() {
setupOverlayViewController1();
- setOverlayViewControllerAsShowing(mOverlayViewController1);
setupOverlayViewController2();
- when(mOverlayViewController2.shouldShowNavigationBar()).thenReturn(true);
+ when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+ when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
+ when(mOverlayViewController2.shouldShowNavigationBarInsets()).thenReturn(true);
mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable);
@@ -226,9 +278,11 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
@Test
public void showView_newHighestZOrder_shouldShowStatusBarFalse_statusBarsHidden() {
setupOverlayViewController1();
- setOverlayViewControllerAsShowing(mOverlayViewController1);
setupOverlayViewController2();
- when(mOverlayViewController2.shouldShowStatusBar()).thenReturn(false);
+ when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+ when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
+ when(mOverlayViewController2.shouldShowStatusBarInsets()).thenReturn(false);
reset(mWindowInsetsController);
mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable);
@@ -239,9 +293,11 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
@Test
public void showView_newHighestZOrder_shouldShowStatusBarTrue_statusBarsShown() {
setupOverlayViewController1();
- setOverlayViewControllerAsShowing(mOverlayViewController1);
setupOverlayViewController2();
- when(mOverlayViewController2.shouldShowStatusBar()).thenReturn(true);
+ when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+ when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
+ when(mOverlayViewController2.shouldShowStatusBarInsets()).thenReturn(true);
mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable);
@@ -289,9 +345,11 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
@Test
public void showView_oldHighestZOrder_shouldShowNavBarFalse_navigationBarsHidden() {
setupOverlayViewController2();
+ when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+ when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
setOverlayViewControllerAsShowing(mOverlayViewController2);
- when(mOverlayViewController1.shouldShowNavigationBar()).thenReturn(true);
- when(mOverlayViewController2.shouldShowNavigationBar()).thenReturn(false);
+ when(mOverlayViewController1.shouldShowNavigationBarInsets()).thenReturn(true);
+ when(mOverlayViewController2.shouldShowNavigationBarInsets()).thenReturn(false);
reset(mWindowInsetsController);
mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
@@ -302,9 +360,11 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
@Test
public void showView_oldHighestZOrder_shouldShowNavBarTrue_navigationBarsShown() {
setupOverlayViewController2();
+ when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+ when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
setOverlayViewControllerAsShowing(mOverlayViewController2);
- when(mOverlayViewController1.shouldShowNavigationBar()).thenReturn(false);
- when(mOverlayViewController2.shouldShowNavigationBar()).thenReturn(true);
+ when(mOverlayViewController1.shouldShowNavigationBarInsets()).thenReturn(false);
+ when(mOverlayViewController2.shouldShowNavigationBarInsets()).thenReturn(true);
mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
@@ -314,9 +374,11 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
@Test
public void showView_oldHighestZOrder_shouldShowStatusBarFalse_statusBarsHidden() {
setupOverlayViewController2();
+ when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+ when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
setOverlayViewControllerAsShowing(mOverlayViewController2);
- when(mOverlayViewController1.shouldShowStatusBar()).thenReturn(true);
- when(mOverlayViewController2.shouldShowStatusBar()).thenReturn(false);
+ when(mOverlayViewController1.shouldShowStatusBarInsets()).thenReturn(true);
+ when(mOverlayViewController2.shouldShowStatusBarInsets()).thenReturn(false);
reset(mWindowInsetsController);
mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
@@ -327,9 +389,11 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
@Test
public void showView_oldHighestZOrder_shouldShowStatusBarTrue_statusBarsShown() {
setupOverlayViewController2();
+ when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+ when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
setOverlayViewControllerAsShowing(mOverlayViewController2);
- when(mOverlayViewController1.shouldShowStatusBar()).thenReturn(false);
- when(mOverlayViewController2.shouldShowStatusBar()).thenReturn(true);
+ when(mOverlayViewController1.shouldShowStatusBarInsets()).thenReturn(false);
+ when(mOverlayViewController2.shouldShowStatusBarInsets()).thenReturn(true);
mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
@@ -512,10 +576,12 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
@Test
public void hideView_newHighestZOrder_shouldShowNavBarFalse_navigationBarHidden() {
setupOverlayViewController1();
- setOverlayViewControllerAsShowing(mOverlayViewController1);
setupOverlayViewController2();
+ when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+ when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
setOverlayViewControllerAsShowing(mOverlayViewController2);
- when(mOverlayViewController1.shouldShowNavigationBar()).thenReturn(false);
+ when(mOverlayViewController1.shouldShowNavigationBarInsets()).thenReturn(false);
reset(mWindowInsetsController);
mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable);
@@ -526,10 +592,13 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
@Test
public void hideView_newHighestZOrder_shouldShowNavBarTrue_navigationBarShown() {
setupOverlayViewController1();
- setOverlayViewControllerAsShowing(mOverlayViewController1);
setupOverlayViewController2();
+ when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+ when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
setOverlayViewControllerAsShowing(mOverlayViewController2);
- when(mOverlayViewController1.shouldShowNavigationBar()).thenReturn(true);
+ when(mOverlayViewController1.shouldShowNavigationBarInsets()).thenReturn(true);
+ reset(mWindowInsetsController);
mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable);
@@ -539,10 +608,12 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
@Test
public void hideView_newHighestZOrder_shouldShowStatusBarFalse_statusBarHidden() {
setupOverlayViewController1();
- setOverlayViewControllerAsShowing(mOverlayViewController1);
setupOverlayViewController2();
+ when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+ when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
setOverlayViewControllerAsShowing(mOverlayViewController2);
- when(mOverlayViewController1.shouldShowStatusBar()).thenReturn(false);
+ when(mOverlayViewController1.shouldShowStatusBarInsets()).thenReturn(false);
reset(mWindowInsetsController);
mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable);
@@ -553,10 +624,13 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
@Test
public void hideView_newHighestZOrder_shouldShowStatusBarTrue_statusBarShown() {
setupOverlayViewController1();
- setOverlayViewControllerAsShowing(mOverlayViewController1);
setupOverlayViewController2();
+ when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+ when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
setOverlayViewControllerAsShowing(mOverlayViewController2);
- when(mOverlayViewController1.shouldShowStatusBar()).thenReturn(true);
+ when(mOverlayViewController1.shouldShowStatusBarInsets()).thenReturn(true);
+ reset(mWindowInsetsController);
mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable);
@@ -593,10 +667,12 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
@Test
public void hideView_oldHighestZOrder_shouldShowNavBarFalse_navigationBarHidden() {
setupOverlayViewController1();
- setOverlayViewControllerAsShowing(mOverlayViewController1);
setupOverlayViewController2();
+ when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+ when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
setOverlayViewControllerAsShowing(mOverlayViewController2);
- when(mOverlayViewController2.shouldShowNavigationBar()).thenReturn(false);
+ when(mOverlayViewController2.shouldShowNavigationBarInsets()).thenReturn(false);
reset(mWindowInsetsController);
mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable);
@@ -607,10 +683,12 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
@Test
public void hideView_oldHighestZOrder_shouldShowNavBarTrue_navigationBarShown() {
setupOverlayViewController1();
- setOverlayViewControllerAsShowing(mOverlayViewController1);
setupOverlayViewController2();
+ when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+ when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
setOverlayViewControllerAsShowing(mOverlayViewController2);
- when(mOverlayViewController2.shouldShowNavigationBar()).thenReturn(true);
+ when(mOverlayViewController2.shouldShowNavigationBarInsets()).thenReturn(true);
mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable);
@@ -620,10 +698,12 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
@Test
public void hideView_oldHighestZOrder_shouldShowStatusBarFalse_statusBarHidden() {
setupOverlayViewController1();
- setOverlayViewControllerAsShowing(mOverlayViewController1);
setupOverlayViewController2();
+ when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+ when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
setOverlayViewControllerAsShowing(mOverlayViewController2);
- when(mOverlayViewController2.shouldShowStatusBar()).thenReturn(false);
+ when(mOverlayViewController2.shouldShowStatusBarInsets()).thenReturn(false);
reset(mWindowInsetsController);
mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable);
@@ -634,10 +714,12 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
@Test
public void hideView_oldHighestZOrder_shouldShowStatusBarTrue_statusBarShown() {
setupOverlayViewController1();
- setOverlayViewControllerAsShowing(mOverlayViewController1);
setupOverlayViewController2();
+ when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+ when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
setOverlayViewControllerAsShowing(mOverlayViewController2);
- when(mOverlayViewController2.shouldShowStatusBar()).thenReturn(true);
+ when(mOverlayViewController2.shouldShowStatusBarInsets()).thenReturn(true);
mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable);
@@ -673,6 +755,7 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
@Test
public void hideView_viewControllerOnlyShown_navigationBarShown() {
setupOverlayViewController1();
+ when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
setOverlayViewControllerAsShowing(mOverlayViewController1);
mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable);
@@ -683,6 +766,7 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
@Test
public void hideView_viewControllerOnlyShown_statusBarShown() {
setupOverlayViewController1();
+ when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
setOverlayViewControllerAsShowing(mOverlayViewController1);
mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable);
diff --git a/packages/InputDevices/res/values-af/strings.xml b/packages/InputDevices/res/values-af/strings.xml
index eb89ee56345a..4bc7bf6e5d1f 100644
--- a/packages/InputDevices/res/values-af/strings.xml
+++ b/packages/InputDevices/res/values-af/strings.xml
@@ -19,6 +19,8 @@
<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_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 2970bfe722d8..7a4ffeff433a 100644
--- a/packages/InputDevices/res/values-am/strings.xml
+++ b/packages/InputDevices/res/values-am/strings.xml
@@ -19,6 +19,8 @@
<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_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 12751dbceb5b..3b00576afd89 100644
--- a/packages/InputDevices/res/values-ar/strings.xml
+++ b/packages/InputDevices/res/values-ar/strings.xml
@@ -19,6 +19,8 @@
<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_italian" msgid="6497079660449781213">"الإيطالية"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"الدانماركية"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"النرويجية"</string>
@@ -26,8 +28,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>
- <!-- no translation found for keyboard_layout_czech_qwerty (3331402534128515501) -->
- <skip />
+ <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>
diff --git a/packages/InputDevices/res/values-as/strings.xml b/packages/InputDevices/res/values-as/strings.xml
index a2df66cb1995..a63821e76633 100644
--- a/packages/InputDevices/res/values-as/strings.xml
+++ b/packages/InputDevices/res/values-as/strings.xml
@@ -19,6 +19,8 @@
<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_italian" msgid="6497079660449781213">"ইটালিয়ান"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"ডেনিশ্ব"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"ন\'ৰৱেয়ান"</string>
@@ -26,8 +28,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>
- <!-- no translation found for keyboard_layout_czech_qwerty (3331402534128515501) -->
- <skip />
+ <string name="keyboard_layout_czech_qwerty" msgid="3331402534128515501">"Czech 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>
diff --git a/packages/InputDevices/res/values-az/strings.xml b/packages/InputDevices/res/values-az/strings.xml
index 1d9eb0687c7d..16badc456e73 100644
--- a/packages/InputDevices/res/values-az/strings.xml
+++ b/packages/InputDevices/res/values-az/strings.xml
@@ -19,6 +19,8 @@
<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_italian" msgid="6497079660449781213">"İtalyan"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Danimarkalı"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norveçli"</string>
@@ -26,8 +28,7 @@
<string name="keyboard_layout_finnish" msgid="5585659438924315466">"Fin"</string>
<string name="keyboard_layout_croatian" msgid="4172229471079281138">"Xorvat"</string>
<string name="keyboard_layout_czech" msgid="1349256901452975343">"Çex"</string>
- <!-- no translation found for keyboard_layout_czech_qwerty (3331402534128515501) -->
- <skip />
+ <string name="keyboard_layout_czech_qwerty" msgid="3331402534128515501">"Çex QWERTY üslubu"</string>
<string name="keyboard_layout_estonian" msgid="8775830985185665274">"Eston"</string>
<string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Macar"</string>
<string name="keyboard_layout_icelandic" msgid="5836645650912489642">"İslandiyalı"</string>
diff --git a/packages/InputDevices/res/values-b+sr+Latn/strings.xml b/packages/InputDevices/res/values-b+sr+Latn/strings.xml
index 7952c22caa67..95ef45916031 100644
--- a/packages/InputDevices/res/values-b+sr+Latn/strings.xml
+++ b/packages/InputDevices/res/values-b+sr+Latn/strings.xml
@@ -19,6 +19,8 @@
<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_italian" msgid="6497079660449781213">"italijanska"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"danska"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"norveška"</string>
@@ -26,8 +28,7 @@
<string name="keyboard_layout_finnish" msgid="5585659438924315466">"finska"</string>
<string name="keyboard_layout_croatian" msgid="4172229471079281138">"hrvatska"</string>
<string name="keyboard_layout_czech" msgid="1349256901452975343">"češka"</string>
- <!-- no translation found for keyboard_layout_czech_qwerty (3331402534128515501) -->
- <skip />
+ <string name="keyboard_layout_czech_qwerty" msgid="3331402534128515501">"Stil češke QWERTY tastature"</string>
<string name="keyboard_layout_estonian" msgid="8775830985185665274">"estonska"</string>
<string name="keyboard_layout_hungarian" msgid="4154963661406035109">"mađarska"</string>
<string name="keyboard_layout_icelandic" msgid="5836645650912489642">"islandska"</string>
diff --git a/packages/InputDevices/res/values-be/strings.xml b/packages/InputDevices/res/values-be/strings.xml
index 4e2d5ba9216f..63b899af1c98 100644
--- a/packages/InputDevices/res/values-be/strings.xml
+++ b/packages/InputDevices/res/values-be/strings.xml
@@ -19,6 +19,8 @@
<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_italian" msgid="6497079660449781213">"Італьянская"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Дацкая"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Нарвежская"</string>
@@ -26,8 +28,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>
- <!-- no translation found for keyboard_layout_czech_qwerty (3331402534128515501) -->
- <skip />
+ <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>
diff --git a/packages/InputDevices/res/values-bg/strings.xml b/packages/InputDevices/res/values-bg/strings.xml
index a1edb21d4b5a..90b7f6c5e9d3 100644
--- a/packages/InputDevices/res/values-bg/strings.xml
+++ b/packages/InputDevices/res/values-bg/strings.xml
@@ -19,6 +19,8 @@
<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_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 d3b47bc3c76b..19292780875e 100644
--- a/packages/InputDevices/res/values-bn/strings.xml
+++ b/packages/InputDevices/res/values-bn/strings.xml
@@ -19,6 +19,8 @@
<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_italian" msgid="6497079660449781213">"ইতালীয়"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"ডেনিশ"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"নরওয়েজীয়"</string>
@@ -26,8 +28,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>
- <!-- no translation found for keyboard_layout_czech_qwerty (3331402534128515501) -->
- <skip />
+ <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>
diff --git a/packages/InputDevices/res/values-bs/strings.xml b/packages/InputDevices/res/values-bs/strings.xml
index 436a3b02eb54..f6a229c34eb4 100644
--- a/packages/InputDevices/res/values-bs/strings.xml
+++ b/packages/InputDevices/res/values-bs/strings.xml
@@ -19,6 +19,8 @@
<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_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 1f089e18d5cb..ec3b247ac6d3 100644
--- a/packages/InputDevices/res/values-ca/strings.xml
+++ b/packages/InputDevices/res/values-ca/strings.xml
@@ -19,6 +19,8 @@
<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_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 b36861a99cc0..99aed5e6ea0e 100644
--- a/packages/InputDevices/res/values-cs/strings.xml
+++ b/packages/InputDevices/res/values-cs/strings.xml
@@ -19,6 +19,8 @@
<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_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 7e446fd44bc4..dbe685cb2329 100644
--- a/packages/InputDevices/res/values-da/strings.xml
+++ b/packages/InputDevices/res/values-da/strings.xml
@@ -19,6 +19,8 @@
<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_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 c8a7ab1f34fe..fd7fca014548 100644
--- a/packages/InputDevices/res/values-de/strings.xml
+++ b/packages/InputDevices/res/values-de/strings.xml
@@ -19,6 +19,8 @@
<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_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 ff7cd022cee7..8bdd6f8bc967 100644
--- a/packages/InputDevices/res/values-el/strings.xml
+++ b/packages/InputDevices/res/values-el/strings.xml
@@ -19,6 +19,8 @@
<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_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-en-rAU/strings.xml b/packages/InputDevices/res/values-en-rAU/strings.xml
index 02a8e6d4138f..f79c3daf25ef 100644
--- a/packages/InputDevices/res/values-en-rAU/strings.xml
+++ b/packages/InputDevices/res/values-en-rAU/strings.xml
@@ -19,6 +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>
+ <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-en-rCA/strings.xml b/packages/InputDevices/res/values-en-rCA/strings.xml
index 02a8e6d4138f..f79c3daf25ef 100644
--- a/packages/InputDevices/res/values-en-rCA/strings.xml
+++ b/packages/InputDevices/res/values-en-rCA/strings.xml
@@ -19,6 +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>
+ <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-en-rGB/strings.xml b/packages/InputDevices/res/values-en-rGB/strings.xml
index 02a8e6d4138f..f79c3daf25ef 100644
--- a/packages/InputDevices/res/values-en-rGB/strings.xml
+++ b/packages/InputDevices/res/values-en-rGB/strings.xml
@@ -19,6 +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>
+ <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-en-rIN/strings.xml b/packages/InputDevices/res/values-en-rIN/strings.xml
index 02a8e6d4138f..f79c3daf25ef 100644
--- a/packages/InputDevices/res/values-en-rIN/strings.xml
+++ b/packages/InputDevices/res/values-en-rIN/strings.xml
@@ -19,6 +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>
+ <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-en-rXC/strings.xml b/packages/InputDevices/res/values-en-rXC/strings.xml
index a8d342f04258..d0881864efef 100644
--- a/packages/InputDevices/res/values-en-rXC/strings.xml
+++ b/packages/InputDevices/res/values-en-rXC/strings.xml
@@ -19,6 +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>
+ <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-es-rUS/strings.xml b/packages/InputDevices/res/values-es-rUS/strings.xml
index 9090cb7b02b2..5319f968afc3 100644
--- a/packages/InputDevices/res/values-es-rUS/strings.xml
+++ b/packages/InputDevices/res/values-es-rUS/strings.xml
@@ -19,6 +19,8 @@
<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_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 66ea65557538..95b3b1c80cdf 100644
--- a/packages/InputDevices/res/values-es/strings.xml
+++ b/packages/InputDevices/res/values-es/strings.xml
@@ -19,6 +19,8 @@
<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_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 9674a0d1d616..a5fb8ac08502 100644
--- a/packages/InputDevices/res/values-et/strings.xml
+++ b/packages/InputDevices/res/values-et/strings.xml
@@ -19,6 +19,8 @@
<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_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 357b6184648f..1c75a7c0f67f 100644
--- a/packages/InputDevices/res/values-eu/strings.xml
+++ b/packages/InputDevices/res/values-eu/strings.xml
@@ -19,6 +19,8 @@
<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_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 5395d27c2cef..ca7d43a1400f 100644
--- a/packages/InputDevices/res/values-fa/strings.xml
+++ b/packages/InputDevices/res/values-fa/strings.xml
@@ -19,6 +19,8 @@
<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_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 d4b0580dac69..2878c78318f7 100644
--- a/packages/InputDevices/res/values-fi/strings.xml
+++ b/packages/InputDevices/res/values-fi/strings.xml
@@ -19,6 +19,8 @@
<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_italian" msgid="6497079660449781213">"italia"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"tanska"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"norja"</string>
@@ -26,8 +28,7 @@
<string name="keyboard_layout_finnish" msgid="5585659438924315466">"suomi"</string>
<string name="keyboard_layout_croatian" msgid="4172229471079281138">"kroaatti"</string>
<string name="keyboard_layout_czech" msgid="1349256901452975343">"tšekki"</string>
- <!-- no translation found for keyboard_layout_czech_qwerty (3331402534128515501) -->
- <skip />
+ <string name="keyboard_layout_czech_qwerty" msgid="3331402534128515501">"tšekki (QWERTY-tyyli)"</string>
<string name="keyboard_layout_estonian" msgid="8775830985185665274">"viro"</string>
<string name="keyboard_layout_hungarian" msgid="4154963661406035109">"unkari"</string>
<string name="keyboard_layout_icelandic" msgid="5836645650912489642">"islanti"</string>
diff --git a/packages/InputDevices/res/values-fr-rCA/strings.xml b/packages/InputDevices/res/values-fr-rCA/strings.xml
index e714e8380800..697fff6f1339 100644
--- a/packages/InputDevices/res/values-fr-rCA/strings.xml
+++ b/packages/InputDevices/res/values-fr-rCA/strings.xml
@@ -19,6 +19,8 @@
<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_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 0a022f192ea2..2e14019340fc 100644
--- a/packages/InputDevices/res/values-fr/strings.xml
+++ b/packages/InputDevices/res/values-fr/strings.xml
@@ -19,6 +19,8 @@
<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_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 0c86f816fa85..e1ca7cfefa1c 100644
--- a/packages/InputDevices/res/values-gl/strings.xml
+++ b/packages/InputDevices/res/values-gl/strings.xml
@@ -19,6 +19,8 @@
<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_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 8648389db862..bc2ee8303290 100644
--- a/packages/InputDevices/res/values-gu/strings.xml
+++ b/packages/InputDevices/res/values-gu/strings.xml
@@ -19,6 +19,8 @@
<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_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 6e674edbedc1..2ffebdd2c1ff 100644
--- a/packages/InputDevices/res/values-hi/strings.xml
+++ b/packages/InputDevices/res/values-hi/strings.xml
@@ -19,6 +19,8 @@
<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_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 cd2dcc183058..0430f8662014 100644
--- a/packages/InputDevices/res/values-hr/strings.xml
+++ b/packages/InputDevices/res/values-hr/strings.xml
@@ -19,6 +19,8 @@
<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_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 1c7a89a06059..76d10f544280 100644
--- a/packages/InputDevices/res/values-hu/strings.xml
+++ b/packages/InputDevices/res/values-hu/strings.xml
@@ -19,6 +19,8 @@
<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_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 fff9fbc5bfd8..fa4e24550639 100644
--- a/packages/InputDevices/res/values-hy/strings.xml
+++ b/packages/InputDevices/res/values-hy/strings.xml
@@ -19,6 +19,8 @@
<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_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 04a6dfa38bdd..f5d173a338a0 100644
--- a/packages/InputDevices/res/values-in/strings.xml
+++ b/packages/InputDevices/res/values-in/strings.xml
@@ -19,6 +19,8 @@
<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_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 a60332a3f979..09eedd300177 100644
--- a/packages/InputDevices/res/values-is/strings.xml
+++ b/packages/InputDevices/res/values-is/strings.xml
@@ -19,6 +19,8 @@
<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_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 ac137e41b945..e15c01ff6a62 100644
--- a/packages/InputDevices/res/values-it/strings.xml
+++ b/packages/InputDevices/res/values-it/strings.xml
@@ -19,6 +19,8 @@
<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_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 544dde22c2cb..4abdf87ec4d4 100644
--- a/packages/InputDevices/res/values-iw/strings.xml
+++ b/packages/InputDevices/res/values-iw/strings.xml
@@ -19,6 +19,8 @@
<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_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 717cbb985c74..606ab3cd2239 100644
--- a/packages/InputDevices/res/values-ja/strings.xml
+++ b/packages/InputDevices/res/values-ja/strings.xml
@@ -19,6 +19,8 @@
<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_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 ee42b35440b3..b4b1a2df4d30 100644
--- a/packages/InputDevices/res/values-ka/strings.xml
+++ b/packages/InputDevices/res/values-ka/strings.xml
@@ -19,6 +19,8 @@
<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_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 e4ad73e2efd4..cfdc3f85de4a 100644
--- a/packages/InputDevices/res/values-kk/strings.xml
+++ b/packages/InputDevices/res/values-kk/strings.xml
@@ -19,6 +19,8 @@
<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_italian" msgid="6497079660449781213">"Италиян"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Дат"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Норвег"</string>
@@ -26,8 +28,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>
- <!-- no translation found for keyboard_layout_czech_qwerty (3331402534128515501) -->
- <skip />
+ <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>
diff --git a/packages/InputDevices/res/values-km/strings.xml b/packages/InputDevices/res/values-km/strings.xml
index a2c3262559c9..2aaf816c7deb 100644
--- a/packages/InputDevices/res/values-km/strings.xml
+++ b/packages/InputDevices/res/values-km/strings.xml
@@ -19,6 +19,8 @@
<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_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 846101c57c4a..8f62eb36a1f6 100644
--- a/packages/InputDevices/res/values-kn/strings.xml
+++ b/packages/InputDevices/res/values-kn/strings.xml
@@ -19,6 +19,8 @@
<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_italian" msgid="6497079660449781213">"ಇಟಾಲಿಯನ್"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"ಡ್ಯಾನಿಶ್"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"ನಾರ್ವೇಜಿಯನ್"</string>
@@ -26,8 +28,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>
- <!-- no translation found for keyboard_layout_czech_qwerty (3331402534128515501) -->
- <skip />
+ <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>
diff --git a/packages/InputDevices/res/values-ko/strings.xml b/packages/InputDevices/res/values-ko/strings.xml
index 2677432970da..b1f658202c46 100644
--- a/packages/InputDevices/res/values-ko/strings.xml
+++ b/packages/InputDevices/res/values-ko/strings.xml
@@ -19,6 +19,8 @@
<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_italian" msgid="6497079660449781213">"이탈리아어"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"덴마크어"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"노르웨이어"</string>
@@ -26,8 +28,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>
- <!-- no translation found for keyboard_layout_czech_qwerty (3331402534128515501) -->
- <skip />
+ <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>
diff --git a/packages/InputDevices/res/values-ky/strings.xml b/packages/InputDevices/res/values-ky/strings.xml
index 15a504ea4f59..bc521a2cd6cb 100644
--- a/packages/InputDevices/res/values-ky/strings.xml
+++ b/packages/InputDevices/res/values-ky/strings.xml
@@ -19,6 +19,8 @@
<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_italian" msgid="6497079660449781213">"Италия"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Дания"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Норвег"</string>
@@ -26,8 +28,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>
- <!-- no translation found for keyboard_layout_czech_qwerty (3331402534128515501) -->
- <skip />
+ <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>
diff --git a/packages/InputDevices/res/values-lo/strings.xml b/packages/InputDevices/res/values-lo/strings.xml
index 26e7ad471bad..edb59f3964ed 100644
--- a/packages/InputDevices/res/values-lo/strings.xml
+++ b/packages/InputDevices/res/values-lo/strings.xml
@@ -19,6 +19,8 @@
<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_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 d0b855d10822..f33eb42e6a4f 100644
--- a/packages/InputDevices/res/values-lt/strings.xml
+++ b/packages/InputDevices/res/values-lt/strings.xml
@@ -19,6 +19,8 @@
<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_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 f3758390380d..4f47a3ba65db 100644
--- a/packages/InputDevices/res/values-lv/strings.xml
+++ b/packages/InputDevices/res/values-lv/strings.xml
@@ -19,6 +19,8 @@
<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_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>
@@ -26,8 +28,7 @@
<string name="keyboard_layout_finnish" msgid="5585659438924315466">"Somu"</string>
<string name="keyboard_layout_croatian" msgid="4172229471079281138">"Horvātu"</string>
<string name="keyboard_layout_czech" msgid="1349256901452975343">"Čehu"</string>
- <!-- no translation found for keyboard_layout_czech_qwerty (3331402534128515501) -->
- <skip />
+ <string name="keyboard_layout_czech_qwerty" msgid="3331402534128515501">"Čehu (QWERTY)"</string>
<string name="keyboard_layout_estonian" msgid="8775830985185665274">"Igauņu"</string>
<string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Ungāru"</string>
<string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Īslandiešu"</string>
diff --git a/packages/InputDevices/res/values-mk/strings.xml b/packages/InputDevices/res/values-mk/strings.xml
index ce5e8f22f8ef..c036409c9c62 100644
--- a/packages/InputDevices/res/values-mk/strings.xml
+++ b/packages/InputDevices/res/values-mk/strings.xml
@@ -19,6 +19,8 @@
<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_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 cb65e9c40822..65fbf22d5fe7 100644
--- a/packages/InputDevices/res/values-ml/strings.xml
+++ b/packages/InputDevices/res/values-ml/strings.xml
@@ -19,6 +19,8 @@
<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_italian" msgid="6497079660449781213">"ഇറ്റാലിയൻ"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"ഡാനിഷ്"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"നോർവീജിയൻ"</string>
@@ -26,8 +28,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>
- <!-- no translation found for keyboard_layout_czech_qwerty (3331402534128515501) -->
- <skip />
+ <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>
diff --git a/packages/InputDevices/res/values-mn/strings.xml b/packages/InputDevices/res/values-mn/strings.xml
index f405750e941d..a8fc6615f4c1 100644
--- a/packages/InputDevices/res/values-mn/strings.xml
+++ b/packages/InputDevices/res/values-mn/strings.xml
@@ -19,6 +19,8 @@
<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_italian" msgid="6497079660449781213">"Итали"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Дани"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Норвеги"</string>
@@ -26,8 +28,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>
- <!-- no translation found for keyboard_layout_czech_qwerty (3331402534128515501) -->
- <skip />
+ <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>
diff --git a/packages/InputDevices/res/values-mr/strings.xml b/packages/InputDevices/res/values-mr/strings.xml
index ba31233e0006..da6caab3842e 100644
--- a/packages/InputDevices/res/values-mr/strings.xml
+++ b/packages/InputDevices/res/values-mr/strings.xml
@@ -19,6 +19,8 @@
<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_italian" msgid="6497079660449781213">"इटालियन"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"डॅनिश"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"नॉर्वेजियन"</string>
@@ -26,8 +28,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>
- <!-- no translation found for keyboard_layout_czech_qwerty (3331402534128515501) -->
- <skip />
+ <string name="keyboard_layout_czech_qwerty" msgid="3331402534128515501">"Czech 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>
diff --git a/packages/InputDevices/res/values-ms/strings.xml b/packages/InputDevices/res/values-ms/strings.xml
index 09afb200773f..975024ba1cdf 100644
--- a/packages/InputDevices/res/values-ms/strings.xml
+++ b/packages/InputDevices/res/values-ms/strings.xml
@@ -19,6 +19,8 @@
<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_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>
@@ -26,8 +28,7 @@
<string name="keyboard_layout_finnish" msgid="5585659438924315466">"Bahasa Finland"</string>
<string name="keyboard_layout_croatian" msgid="4172229471079281138">"Bahasa Croatia"</string>
<string name="keyboard_layout_czech" msgid="1349256901452975343">"Bahasa Czech"</string>
- <!-- no translation found for keyboard_layout_czech_qwerty (3331402534128515501) -->
- <skip />
+ <string name="keyboard_layout_czech_qwerty" msgid="3331402534128515501">"Gaya QWERTY Czech"</string>
<string name="keyboard_layout_estonian" msgid="8775830985185665274">"Bahasa Estonia"</string>
<string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Bahasa Hungary"</string>
<string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Bahasa Iceland"</string>
diff --git a/packages/InputDevices/res/values-my/strings.xml b/packages/InputDevices/res/values-my/strings.xml
index 57748dbfa4e1..5484d9d2a2e8 100644
--- a/packages/InputDevices/res/values-my/strings.xml
+++ b/packages/InputDevices/res/values-my/strings.xml
@@ -19,6 +19,8 @@
<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_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 6059b4c8a822..54840338b966 100644
--- a/packages/InputDevices/res/values-nb/strings.xml
+++ b/packages/InputDevices/res/values-nb/strings.xml
@@ -19,6 +19,8 @@
<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_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 731c88fe4077..24816c1126e3 100644
--- a/packages/InputDevices/res/values-ne/strings.xml
+++ b/packages/InputDevices/res/values-ne/strings.xml
@@ -19,6 +19,8 @@
<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_italian" msgid="6497079660449781213">"इटालियन"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"डेनिश"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"नर्वेजियन"</string>
@@ -26,8 +28,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>
- <!-- no translation found for keyboard_layout_czech_qwerty (3331402534128515501) -->
- <skip />
+ <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>
diff --git a/packages/InputDevices/res/values-nl/strings.xml b/packages/InputDevices/res/values-nl/strings.xml
index 9767f92d72e4..e000a30f9cf9 100644
--- a/packages/InputDevices/res/values-nl/strings.xml
+++ b/packages/InputDevices/res/values-nl/strings.xml
@@ -19,6 +19,8 @@
<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_italian" msgid="6497079660449781213">"Italiaans"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Deens"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Noors"</string>
@@ -26,7 +28,7 @@
<string name="keyboard_layout_finnish" msgid="5585659438924315466">"Fins"</string>
<string name="keyboard_layout_croatian" msgid="4172229471079281138">"Kroatisch"</string>
<string name="keyboard_layout_czech" msgid="1349256901452975343">"Tsjechisch"</string>
- <string name="keyboard_layout_czech_qwerty" msgid="3331402534128515501">"Tsjechisch - QWERTY-stijl"</string>
+ <string name="keyboard_layout_czech_qwerty" msgid="3331402534128515501">"Tsjechisch - QWERTY-toetsenbord"</string>
<string name="keyboard_layout_estonian" msgid="8775830985185665274">"Estlands"</string>
<string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Hongaars"</string>
<string name="keyboard_layout_icelandic" msgid="5836645650912489642">"IJslands"</string>
diff --git a/packages/InputDevices/res/values-or/strings.xml b/packages/InputDevices/res/values-or/strings.xml
index 04d44dcac860..6185ff77695d 100644
--- a/packages/InputDevices/res/values-or/strings.xml
+++ b/packages/InputDevices/res/values-or/strings.xml
@@ -19,6 +19,8 @@
<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_italian" msgid="6497079660449781213">"ଇଟାଲିୟାନ୍‌"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"ଡାନିଶ୍‍"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"ନରୱେଜିଆନ୍"</string>
@@ -26,8 +28,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>
- <!-- no translation found for keyboard_layout_czech_qwerty (3331402534128515501) -->
- <skip />
+ <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>
diff --git a/packages/InputDevices/res/values-pa/strings.xml b/packages/InputDevices/res/values-pa/strings.xml
index 2eb30c90dd42..97cf28bbc66c 100644
--- a/packages/InputDevices/res/values-pa/strings.xml
+++ b/packages/InputDevices/res/values-pa/strings.xml
@@ -19,6 +19,8 @@
<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_italian" msgid="6497079660449781213">"ਇਤਾਲਵੀ"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"ਡੈਨਿਸ਼"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"ਨਾਰਵੇਜੀਅਨ"</string>
@@ -26,8 +28,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>
- <!-- no translation found for keyboard_layout_czech_qwerty (3331402534128515501) -->
- <skip />
+ <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>
diff --git a/packages/InputDevices/res/values-pl/strings.xml b/packages/InputDevices/res/values-pl/strings.xml
index c3751197b7d2..61819b624112 100644
--- a/packages/InputDevices/res/values-pl/strings.xml
+++ b/packages/InputDevices/res/values-pl/strings.xml
@@ -19,6 +19,8 @@
<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_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 d6687c811a5b..665a1c7632f1 100644
--- a/packages/InputDevices/res/values-pt-rBR/strings.xml
+++ b/packages/InputDevices/res/values-pt-rBR/strings.xml
@@ -19,6 +19,8 @@
<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_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 4f8a43279565..1ccc64409452 100644
--- a/packages/InputDevices/res/values-pt-rPT/strings.xml
+++ b/packages/InputDevices/res/values-pt-rPT/strings.xml
@@ -19,6 +19,8 @@
<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_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 d6687c811a5b..665a1c7632f1 100644
--- a/packages/InputDevices/res/values-pt/strings.xml
+++ b/packages/InputDevices/res/values-pt/strings.xml
@@ -19,6 +19,8 @@
<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_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 1b38bdddd1a9..e0b488585c78 100644
--- a/packages/InputDevices/res/values-ro/strings.xml
+++ b/packages/InputDevices/res/values-ro/strings.xml
@@ -19,6 +19,8 @@
<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_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 501517d34176..41ccf1a4f689 100644
--- a/packages/InputDevices/res/values-ru/strings.xml
+++ b/packages/InputDevices/res/values-ru/strings.xml
@@ -19,6 +19,8 @@
<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_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-si/strings.xml b/packages/InputDevices/res/values-si/strings.xml
index c13661613bd5..4d355d73e985 100644
--- a/packages/InputDevices/res/values-si/strings.xml
+++ b/packages/InputDevices/res/values-si/strings.xml
@@ -19,6 +19,8 @@
<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_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 260a43258818..c7ff2fd5d994 100644
--- a/packages/InputDevices/res/values-sk/strings.xml
+++ b/packages/InputDevices/res/values-sk/strings.xml
@@ -19,6 +19,8 @@
<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_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>
@@ -26,8 +28,7 @@
<string name="keyboard_layout_finnish" msgid="5585659438924315466">"fínske"</string>
<string name="keyboard_layout_croatian" msgid="4172229471079281138">"chorvátske"</string>
<string name="keyboard_layout_czech" msgid="1349256901452975343">"české"</string>
- <!-- no translation found for keyboard_layout_czech_qwerty (3331402534128515501) -->
- <skip />
+ <string name="keyboard_layout_czech_qwerty" msgid="3331402534128515501">"Český štýl QWERTY"</string>
<string name="keyboard_layout_estonian" msgid="8775830985185665274">"estónske"</string>
<string name="keyboard_layout_hungarian" msgid="4154963661406035109">"maďarské"</string>
<string name="keyboard_layout_icelandic" msgid="5836645650912489642">"islandské"</string>
diff --git a/packages/InputDevices/res/values-sl/strings.xml b/packages/InputDevices/res/values-sl/strings.xml
index 91ba472bde1e..68741b42e880 100644
--- a/packages/InputDevices/res/values-sl/strings.xml
+++ b/packages/InputDevices/res/values-sl/strings.xml
@@ -19,6 +19,8 @@
<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_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 3128025d6bd0..b3677cd699b4 100644
--- a/packages/InputDevices/res/values-sq/strings.xml
+++ b/packages/InputDevices/res/values-sq/strings.xml
@@ -19,6 +19,8 @@
<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_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 686f6aea8ce1..2f68903c4e2d 100644
--- a/packages/InputDevices/res/values-sr/strings.xml
+++ b/packages/InputDevices/res/values-sr/strings.xml
@@ -19,6 +19,8 @@
<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_italian" msgid="6497079660449781213">"италијанска"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"данска"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"норвешка"</string>
@@ -26,8 +28,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>
- <!-- no translation found for keyboard_layout_czech_qwerty (3331402534128515501) -->
- <skip />
+ <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>
diff --git a/packages/InputDevices/res/values-sv/strings.xml b/packages/InputDevices/res/values-sv/strings.xml
index a4c0fcaa86de..b465fa64ce82 100644
--- a/packages/InputDevices/res/values-sv/strings.xml
+++ b/packages/InputDevices/res/values-sv/strings.xml
@@ -19,6 +19,8 @@
<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_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 1e5c8d7d30e1..794d907f006d 100644
--- a/packages/InputDevices/res/values-sw/strings.xml
+++ b/packages/InputDevices/res/values-sw/strings.xml
@@ -19,6 +19,8 @@
<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_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 e25e5edfda40..b75b57d5f358 100644
--- a/packages/InputDevices/res/values-ta/strings.xml
+++ b/packages/InputDevices/res/values-ta/strings.xml
@@ -19,6 +19,8 @@
<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_italian" msgid="6497079660449781213">"இத்தாலியன்"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"டேனிஷ்"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"நார்வேஜியன்"</string>
@@ -26,8 +28,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>
- <!-- no translation found for keyboard_layout_czech_qwerty (3331402534128515501) -->
- <skip />
+ <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>
diff --git a/packages/InputDevices/res/values-te/strings.xml b/packages/InputDevices/res/values-te/strings.xml
index b6caafd48790..d40c3e0d5d9f 100644
--- a/packages/InputDevices/res/values-te/strings.xml
+++ b/packages/InputDevices/res/values-te/strings.xml
@@ -19,6 +19,8 @@
<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_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 1170f8620a39..59136506abe0 100644
--- a/packages/InputDevices/res/values-th/strings.xml
+++ b/packages/InputDevices/res/values-th/strings.xml
@@ -19,6 +19,8 @@
<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_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 97b878bde52c..21ea5de903bd 100644
--- a/packages/InputDevices/res/values-tl/strings.xml
+++ b/packages/InputDevices/res/values-tl/strings.xml
@@ -19,6 +19,8 @@
<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_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 7e9b5e50fbc6..a89cea59b30f 100644
--- a/packages/InputDevices/res/values-tr/strings.xml
+++ b/packages/InputDevices/res/values-tr/strings.xml
@@ -19,6 +19,8 @@
<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_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 db5de4cb7a83..4b37ca954d37 100644
--- a/packages/InputDevices/res/values-uk/strings.xml
+++ b/packages/InputDevices/res/values-uk/strings.xml
@@ -19,6 +19,8 @@
<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_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 bf9e5adca553..ca42086b8811 100644
--- a/packages/InputDevices/res/values-ur/strings.xml
+++ b/packages/InputDevices/res/values-ur/strings.xml
@@ -19,6 +19,8 @@
<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_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 9d7f1e43e93f..77a06b5a4d79 100644
--- a/packages/InputDevices/res/values-uz/strings.xml
+++ b/packages/InputDevices/res/values-uz/strings.xml
@@ -19,6 +19,8 @@
<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_italian" msgid="6497079660449781213">"Italyancha"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Datcha"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norvegcha"</string>
@@ -26,8 +28,7 @@
<string name="keyboard_layout_finnish" msgid="5585659438924315466">"Fincha"</string>
<string name="keyboard_layout_croatian" msgid="4172229471079281138">"Xorvatcha"</string>
<string name="keyboard_layout_czech" msgid="1349256901452975343">"Chexcha"</string>
- <!-- no translation found for keyboard_layout_czech_qwerty (3331402534128515501) -->
- <skip />
+ <string name="keyboard_layout_czech_qwerty" msgid="3331402534128515501">"Chex QWERTY uslubi"</string>
<string name="keyboard_layout_estonian" msgid="8775830985185665274">"Estoncha"</string>
<string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Vengercha"</string>
<string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Islandcha"</string>
diff --git a/packages/InputDevices/res/values-vi/strings.xml b/packages/InputDevices/res/values-vi/strings.xml
index eabaa1961946..fd570efe8693 100644
--- a/packages/InputDevices/res/values-vi/strings.xml
+++ b/packages/InputDevices/res/values-vi/strings.xml
@@ -19,6 +19,8 @@
<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_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 3eb4b2c94a97..afc373acd61f 100644
--- a/packages/InputDevices/res/values-zh-rCN/strings.xml
+++ b/packages/InputDevices/res/values-zh-rCN/strings.xml
@@ -19,6 +19,8 @@
<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_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 37cd533ca9b9..775fa2acfcec 100644
--- a/packages/InputDevices/res/values-zh-rHK/strings.xml
+++ b/packages/InputDevices/res/values-zh-rHK/strings.xml
@@ -19,6 +19,8 @@
<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_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 8d2de40b2d86..b4a059cbeb1f 100644
--- a/packages/InputDevices/res/values-zh-rTW/strings.xml
+++ b/packages/InputDevices/res/values-zh-rTW/strings.xml
@@ -19,6 +19,8 @@
<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_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 ddb688ad92e9..0a2499ad8967 100644
--- a/packages/InputDevices/res/values-zu/strings.xml
+++ b/packages/InputDevices/res/values-zu/strings.xml
@@ -19,6 +19,8 @@
<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_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/SettingsLib/HelpUtils/res/values-te/strings.xml b/packages/SettingsLib/HelpUtils/res/values-te/strings.xml
index ea66717b1e87..82c8613b5c8c 100644
--- a/packages/SettingsLib/HelpUtils/res/values-te/strings.xml
+++ b/packages/SettingsLib/HelpUtils/res/values-te/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="help_feedback_label" msgid="7106780063063027882">"సహాయం &amp; అభిప్రాయం"</string>
+ <string name="help_feedback_label" msgid="7106780063063027882">"సహాయం &amp; ఫీడ్‌బ్యాక్"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 9ca1814783b5..0b4538097e41 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -153,7 +153,7 @@
<string name="unknown" msgid="3544487229740637809">"غير معروف"</string>
<string name="running_process_item_user_label" msgid="3988506293099805796">"المستخدم: <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
<string name="launch_defaults_some" msgid="3631650616557252926">"تم ضبط بعض الإعدادات التلقائية"</string>
- <string name="launch_defaults_none" msgid="8049374306261262709">"لم يتم تعيين إعدادات تلقائية"</string>
+ <string name="launch_defaults_none" msgid="8049374306261262709">"لم يتم ضبط إعدادات تلقائية"</string>
<string name="tts_settings" msgid="8130616705989351312">"إعدادات تحويل النص إلى كلام"</string>
<string name="tts_settings_title" msgid="7602210956640483039">"تحويل النص إلى كلام"</string>
<string name="tts_default_rate_title" msgid="3964187817364304022">"معدل سرعة الكلام"</string>
@@ -245,7 +245,7 @@
<string name="confirm_enable_oem_unlock_title" msgid="8249318129774367535">"هل تريد السماح بإلغاء قفل المصنّع الأصلي للجهاز؟"</string>
<string name="confirm_enable_oem_unlock_text" msgid="854131050791011970">"تحذير: لن تعمل ميزات الحماية على هذا الجهاز أثناء تفعيل هذا الإعداد."</string>
<string name="mock_location_app" msgid="6269380172542248304">"اختيار تطبيق الموقع الزائف"</string>
- <string name="mock_location_app_not_set" msgid="6972032787262831155">"لم يتم تعيين تطبيق موقع زائف"</string>
+ <string name="mock_location_app_not_set" msgid="6972032787262831155">"لم يتم ضبط تطبيق موقع زائف"</string>
<string name="mock_location_app_set" msgid="4706722469342913843">"تطبيق الموقع الزائف: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="debug_networking_category" msgid="6829757985772659599">"الشبكات"</string>
<string name="wifi_display_certification" msgid="1805579519992520381">"شهادة عرض شاشة لاسلكي"</string>
@@ -318,7 +318,7 @@
<string name="hdcp_checking_dialog_title" msgid="7691060297616217781">"‏تعيين سلوك التحقق من HDCP"</string>
<string name="debug_debugging_category" msgid="535341063709248842">"تصحيح الأخطاء"</string>
<string name="debug_app" msgid="8903350241392391766">"اختيار التطبيق لتصحيحه"</string>
- <string name="debug_app_not_set" msgid="1934083001283807188">"لم يتم تعيين تطبيق لتصحيحه"</string>
+ <string name="debug_app_not_set" msgid="1934083001283807188">"لم يتم ضبط تطبيق لتصحيحه"</string>
<string name="debug_app_set" msgid="6599535090477753651">"تطبيق التصحيح: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="select_application" msgid="2543228890535466325">"اختيار تطبيق"</string>
<string name="no_application" msgid="9038334538870247690">"لا شيء"</string>
@@ -381,9 +381,9 @@
<string name="local_backup_password_title" msgid="4631017948933578709">"كلمة مرور احتياطية للكمبيوتر"</string>
<string name="local_backup_password_summary_none" msgid="7646898032616361714">"النُسخ الاحتياطية الكاملة لسطح المكتب غير محمية في الوقت الحالي"</string>
<string name="local_backup_password_summary_change" msgid="1707357670383995567">"انقر لتغيير كلمة مرور النسخ الاحتياطية الكاملة لسطح المكتب أو إزالتها."</string>
- <string name="local_backup_password_toast_success" msgid="4891666204428091604">"تم تعيين كلمة مرور احتياطية جديدة"</string>
+ <string name="local_backup_password_toast_success" msgid="4891666204428091604">"تم ضبط كلمة مرور احتياطية جديدة"</string>
<string name="local_backup_password_toast_confirmation_mismatch" msgid="2994718182129097733">"كلمة المرور الجديدة وتأكيدها لا يتطابقان"</string>
- <string name="local_backup_password_toast_validation_failure" msgid="714669442363647122">"تعذّر تعيين كلمة مرور احتياطية"</string>
+ <string name="local_backup_password_toast_validation_failure" msgid="714669442363647122">"تعذّر ضبط كلمة مرور احتياطية"</string>
<string name="loading_injected_setting_summary" msgid="8394446285689070348">"جارٍ التحميل…"</string>
<string-array name="color_mode_names">
<item msgid="3836559907767149216">"نابض بالحياة (تلقائي)"</item>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index 87f3b7a4f2e8..1ab88ed8e00e 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -487,8 +487,8 @@
<string name="status_unavailable" msgid="5279036186589861608">"অনুপলভ্য"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC র‍্যান্ডমাইজ করা হয়েছে"</string>
<plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">%1$dটি ডিভাইস কানেক্ট</item>
- <item quantity="other">%1$dটি ডিভাইস কানেক্ট</item>
+ <item quantity="one">%1$dটি ডিভাইস কানেক্ট রয়েছে</item>
+ <item quantity="other">%1$dটি ডিভাইস কানেক্ট রয়েছে</item>
</plurals>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"আরও বেশি।"</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"আরও কম।"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index 5fc311af26b2..3373f815ebaf 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -211,8 +211,8 @@
<string name="adb_wireless_error" msgid="721958772149779856">"خطا"</string>
<string name="adb_wireless_settings" msgid="2295017847215680229">"اشکال‌زدایی بی‌سیم"</string>
<string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"برای مشاهده و استفاده از دستگاه‌های در دسترس، اشکال‌زدایی بی‌سیم را روشن کنید"</string>
- <string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"‏مرتبط کردن دستگاه با کد QR"</string>
- <string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"‏دستگاه‌های جدید را بااستفاده از اسکنر کد QR مرتبط کنید"</string>
+ <string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"مرتبط کردن دستگاه با رمزینه پاسخ‌سریع"</string>
+ <string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"دستگاه‌های جدید را بااستفاده از اسکنر رمزینه پاسخ‌سریع مرتبط کنید"</string>
<string name="adb_pair_method_code_title" msgid="1122590300445142904">"مرتبط کردن دستگاه با کد مرتبط‌سازی"</string>
<string name="adb_pair_method_code_summary" msgid="6370414511333685185">"دستگاه‌های جدید را با استفاده از کد شش رقمی مرتبط کنید"</string>
<string name="adb_paired_devices_title" msgid="5268997341526217362">"دستگاه‌های مرتبط‌شده"</string>
@@ -226,12 +226,12 @@
<string name="adb_pairing_device_dialog_pairing_code_label" msgid="3639239786669722731">"‏کد مرتبط‌سازی Wi‑Fi"</string>
<string name="adb_pairing_device_dialog_failed_title" msgid="3426758947882091735">"مرتبط‌سازی ناموفق"</string>
<string name="adb_pairing_device_dialog_failed_msg" msgid="6611097519661997148">"مطمئن شوید که دستگاه به همان شبکه متصل باشد."</string>
- <string name="adb_wireless_qrcode_summary" msgid="8051414549011801917">"‏دستگاه را ازطریق Wi‑Fi و با اسکن کردن کد QR مرتبط کنید"</string>
+ <string name="adb_wireless_qrcode_summary" msgid="8051414549011801917">"‏دستگاه را ازطریق Wi‑Fi و با اسکن کردن رمزینه پاسخ‌سریع مرتبط کنید"</string>
<string name="adb_wireless_verifying_qrcode_text" msgid="6123192424916029207">"مرتبط‌سازی دستگاه…"</string>
- <string name="adb_qrcode_pairing_device_failed_msg" msgid="6936292092592914132">"‏مرتبط کردن دستگاه انجام نشد. یا کد QR اشتباه بوده است، یا دستگاه به همان شبکه متصل نیست."</string>
+ <string name="adb_qrcode_pairing_device_failed_msg" msgid="6936292092592914132">"مرتبط کردن دستگاه انجام نشد. یا رمزینه پاسخ‌سریع اشتباه بوده است، یا دستگاه به همان شبکه متصل نیست."</string>
<string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"‏نشانی IP و درگاه"</string>
- <string name="adb_wireless_qrcode_pairing_title" msgid="1906409667944674707">"‏اسکن کد QR"</string>
- <string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"‏دستگاه را ازطریق Wi‑Fi و با اسکن کردن کد QR مرتبط کنید"</string>
+ <string name="adb_wireless_qrcode_pairing_title" msgid="1906409667944674707">"اسکن رمزینه پاسخ‌سریع"</string>
+ <string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"‏دستگاه را ازطریق Wi‑Fi و با اسکن کردن رمزینه پاسخ‌سریع مرتبط کنید"</string>
<string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"‏لطفاً به شبکه Wi-Fi متصل شوید"</string>
<string name="keywords_adb_wireless" msgid="6507505581882171240">"‏ADB (پل اشکال‌زدایی Android)، اشکال‌زدایی کردن، برنامه‌نویس"</string>
<string name="bugreport_in_power" msgid="8664089072534638709">"میان‌بر گزارش مشکل"</string>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index 3261f69b1105..894a14ac3b74 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -487,8 +487,8 @@
<string name="status_unavailable" msgid="5279036186589861608">"અનુપલબ્ધ"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MACને રેન્ડમ કરેલ છે"</string>
<plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">%1$d ઉપકરણ કનેક્ટ કર્યું</item>
- <item quantity="other">%1$d ઉપકરણો કનેક્ટ કર્યા</item>
+ <item quantity="one">%1$d ડિવાઇસ કનેક્ટ કર્યું</item>
+ <item quantity="other">%1$d ડિવાઇસ કનેક્ટ કર્યા</item>
</plurals>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"વધુ સમય."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"ઓછો સમય."</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 904a70e73958..6e734bc873b8 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -487,7 +487,7 @@
<string name="status_unavailable" msgid="5279036186589861608">"मौजूद नहीं है"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"एमएसी पता रैंडम पर सेट है"</string>
<plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">%1$d डिवाइस जुड़े हैं</item>
+ <item quantity="one">%1$d डिवाइस जुड़ा है</item>
<item quantity="other">%1$d डिवाइस जुड़े हैं</item>
</plurals>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"ज़्यादा समय."</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index fec2dd6d523f..d86d88b3345d 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -145,7 +145,7 @@
<string name="data_usage_ota" msgid="7984667793701597001">"Rendszerfrissítések"</string>
<string name="tether_settings_title_usb" msgid="3728686573430917722">"USB-megosztás"</string>
<string name="tether_settings_title_wifi" msgid="4803402057533895526">"Hordozható hotspot"</string>
- <string name="tether_settings_title_bluetooth" msgid="916519902721399656">"Bluetooth megosztása"</string>
+ <string name="tether_settings_title_bluetooth" msgid="916519902721399656">"Bluetooth-megosztás"</string>
<string name="tether_settings_title_usb_bluetooth" msgid="1727111807207577322">"Megosztás"</string>
<string name="tether_settings_title_all" msgid="8910259483383010470">"Megosztás és hotspot"</string>
<string name="managed_user_title" msgid="449081789742645723">"Összes munkaalkalmazás"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index 8b92640e9996..ffd95a45b61e 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -57,7 +57,7 @@
<string name="osu_sign_up_complete" msgid="7640183358878916847">"Գրանցումն ավարտված է: Միացում…"</string>
<string name="speed_label_very_slow" msgid="8526005255731597666">"Շատ դանդաղ"</string>
<string name="speed_label_slow" msgid="6069917670665664161">"Դանդաղ"</string>
- <string name="speed_label_okay" msgid="1253594383880810424">"Հաստատել"</string>
+ <string name="speed_label_okay" msgid="1253594383880810424">"Լավ"</string>
<string name="speed_label_medium" msgid="9078405312828606976">"Միջին"</string>
<string name="speed_label_fast" msgid="2677719134596044051">"Արագ"</string>
<string name="speed_label_very_fast" msgid="8215718029533182439">"Շատ արագ"</string>
@@ -493,7 +493,7 @@
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Ավելացնել ժամանակը:"</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Պակասեցնել ժամանակը:"</string>
<string name="cancel" msgid="5665114069455378395">"Չեղարկել"</string>
- <string name="okay" msgid="949938843324579502">"Հաստատել"</string>
+ <string name="okay" msgid="949938843324579502">"Եղավ"</string>
<string name="zen_mode_enable_dialog_turn_on" msgid="6418297231575050426">"Միացնել"</string>
<string name="zen_mode_settings_turn_on_dialog_title" msgid="2760567063190790696">"Միացրեք «Չանհանգստացնել» ռեժիմը"</string>
<string name="zen_mode_settings_summary_off" msgid="3832876036123504076">"Երբեք"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 762d8304dd64..ee368fd5bfcf 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -143,9 +143,9 @@
<string name="data_usage_uninstalled_apps" msgid="1933665711856171491">"हटाइएका एपहरू"</string>
<string name="data_usage_uninstalled_apps_users" msgid="5533981546921913295">"एपहरू र प्रयोगकर्ताहरू हटाइयो।"</string>
<string name="data_usage_ota" msgid="7984667793701597001">"प्रणालीसम्बन्धी अद्यावधिकहरू"</string>
- <string name="tether_settings_title_usb" msgid="3728686573430917722">"USB टेथर गर्दै"</string>
+ <string name="tether_settings_title_usb" msgid="3728686573430917722">"USB टेदर गर्दै"</string>
<string name="tether_settings_title_wifi" msgid="4803402057533895526">"पोर्टेबल हटस्पट"</string>
- <string name="tether_settings_title_bluetooth" msgid="916519902721399656">"ब्लुटुथ टेथर गर्दै"</string>
+ <string name="tether_settings_title_bluetooth" msgid="916519902721399656">"ब्लुटुथ टेदर गर्दै"</string>
<string name="tether_settings_title_usb_bluetooth" msgid="1727111807207577322">"टेदर गर्दै"</string>
<string name="tether_settings_title_all" msgid="8910259483383010470">"टेदर गर्ने र पोर्टेबल हटस्पट"</string>
<string name="managed_user_title" msgid="449081789742645723">"कार्य प्रोफाइलका सबै एपहरू"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index d2891a06402a..c5d70ac866b1 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -143,11 +143,11 @@
<string name="data_usage_uninstalled_apps" msgid="1933665711856171491">"Programu zilizoondolewa"</string>
<string name="data_usage_uninstalled_apps_users" msgid="5533981546921913295">"Watumiaji na programu ziilizoondolewa"</string>
<string name="data_usage_ota" msgid="7984667793701597001">"Masasisho ya mfumo"</string>
- <string name="tether_settings_title_usb" msgid="3728686573430917722">"Shiriki intaneti kwa USB"</string>
+ <string name="tether_settings_title_usb" msgid="3728686573430917722">"Sambaza mtandao kwa USB"</string>
<string name="tether_settings_title_wifi" msgid="4803402057533895526">"Intaneti ya kusambazwa"</string>
- <string name="tether_settings_title_bluetooth" msgid="916519902721399656">"Shiriki intaneti kwa Bluetooth"</string>
- <string name="tether_settings_title_usb_bluetooth" msgid="1727111807207577322">"Inazuia"</string>
- <string name="tether_settings_title_all" msgid="8910259483383010470">"Kushiriki na kusambaza intaneti"</string>
+ <string name="tether_settings_title_bluetooth" msgid="916519902721399656">"Sambaza mtandao kwa Bluetooth"</string>
+ <string name="tether_settings_title_usb_bluetooth" msgid="1727111807207577322">"Kusambaza mtandao"</string>
+ <string name="tether_settings_title_all" msgid="8910259483383010470">"Kushiriki na kusambaza mtandao"</string>
<string name="managed_user_title" msgid="449081789742645723">"Programu zote za kazini"</string>
<string name="user_guest" msgid="6939192779649870792">"Mgeni"</string>
<string name="unknown" msgid="3544487229740637809">"Haijulikani"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 60001a0b0ecd..e252eca5d72a 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -510,7 +510,7 @@
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"ఫోన్ స్పీకర్"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"కనెక్ట్ చేయడంలో సమస్య ఉంది. పరికరాన్ని ఆఫ్ చేసి, ఆపై తిరిగి ఆన్ చేయండి"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"వైర్ గల ఆడియో పరికరం"</string>
- <string name="help_label" msgid="3528360748637781274">"సహాయం &amp; అభిప్రాయం"</string>
+ <string name="help_label" msgid="3528360748637781274">"సహాయం &amp; ఫీడ్‌బ్యాక్"</string>
<string name="storage_category" msgid="2287342585424631813">"నిల్వ"</string>
<string name="shared_data_title" msgid="1017034836800864953">"షేర్ చేసిన డేటా"</string>
<string name="shared_data_summary" msgid="5516326713822885652">"షేర్ చేసిన డేటాను చూసి, సవరించండి"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index defc33ee9233..7468d0450a7e 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -143,7 +143,7 @@
<string name="data_usage_uninstalled_apps" msgid="1933665711856171491">"แอปพลิเคชันที่นำออก"</string>
<string name="data_usage_uninstalled_apps_users" msgid="5533981546921913295">"แอปพลิเคชันและผู้ใช้ที่นำออก"</string>
<string name="data_usage_ota" msgid="7984667793701597001">"การอัปเดตระบบ"</string>
- <string name="tether_settings_title_usb" msgid="3728686573430917722">"ปล่อยสัญญาณผ่าน USB"</string>
+ <string name="tether_settings_title_usb" msgid="3728686573430917722">"เชื่อมต่อเน็ตผ่าน USB"</string>
<string name="tether_settings_title_wifi" msgid="4803402057533895526">"ฮอตสปอตแบบพกพาได้"</string>
<string name="tether_settings_title_bluetooth" msgid="916519902721399656">"ปล่อยสัญญาณบลูทูธ"</string>
<string name="tether_settings_title_usb_bluetooth" msgid="1727111807207577322">"การปล่อยสัญญาณ"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index 35bbbc0e8b39..34fdc1e45567 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -543,12 +543,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/MediaOutputSliceConstants.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java
index 0d54d7ee9b5c..28e993da7dc4 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";
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index dd2aa3bbbdf5..e7ec8b4e1f65 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,
@@ -2284,6 +2283,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,
@@ -2452,6 +2463,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,
@@ -2572,22 +2586,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/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index d8f772d7f440..91510f65316f 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,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 190015cebe30..f5f58efb72e6 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -323,6 +323,9 @@
<!-- 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" />
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 57c15e3211e1..a1b9dcd9c028 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -772,7 +772,7 @@
</intent-filter>
</receiver>
- <receiver android:name=".media.dialog.MediaOutDialogReceiver"
+ <receiver android:name=".media.dialog.MediaOutputDialogReceiver"
android:exported="true">
<intent-filter>
<action android:name="com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_DIALOG" />
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index e246917842b0..26cead206310 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -86,6 +86,10 @@ public interface QSTile {
*/
InstanceId getInstanceId();
+ default boolean isTileReady() {
+ return false;
+ }
+
@ProvidesInterface(version = Callback.VERSION)
public interface Callback {
public static final int VERSION = 1;
diff --git a/packages/SystemUI/res-keyguard/values-te/strings.xml b/packages/SystemUI/res-keyguard/values-te/strings.xml
index 74386bc7a487..d44003bc5b95 100644
--- a/packages/SystemUI/res-keyguard/values-te/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-te/strings.xml
@@ -134,7 +134,7 @@
<item quantity="other">SIM ఇప్పుడు నిలిపివేయబడింది. PUK కోడ్‌ను నమోదు చేయండి. SIM శాశ్వతంగా నిరుపయోగం కాకుండా ఉండటానికి మీకు <xliff:g id="_NUMBER_1">%d</xliff:g> ప్రయత్నాలు మిగిలి ఉన్నాయి. వివరాల కోసం కారియర్‌ను సంప్రదించండి.</item>
<item quantity="one">SIM ఇప్పుడు నిలిపివేయబడింది. PUK కోడ్‌ను నమోదు చేయండి. SIM శాశ్వతంగా నిరుపయోగం కాకుండా ఉండటానికి మీకు <xliff:g id="_NUMBER_0">%d</xliff:g> ప్రయత్నం మిగిలి ఉంది వివరాల కోసం కారియర్‌ను సంప్రదించండి.</item>
</plurals>
- <string name="clock_title_default" msgid="6342735240617459864">"డిఫాల్ట్"</string>
+ <string name="clock_title_default" msgid="6342735240617459864">"ఆటోమేటిక్"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"బబుల్"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"ఎనలాగ్"</string>
</resources>
diff --git a/packages/SystemUI/res/drawable/ic_move.xml b/packages/SystemUI/res/drawable/ic_move_magnification.xml
index e82c9d0f23a9..ed97d0cc0522 100644
--- a/packages/SystemUI/res/drawable/ic_move.xml
+++ b/packages/SystemUI/res/drawable/ic_move_magnification.xml
@@ -16,7 +16,7 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
- <shape android:shape="oval">
+ <shape android:shape="rectangle">
<solid
android:color="@android:color/black" />
<size
diff --git a/packages/SystemUI/res/layout/window_magnifier_view.xml b/packages/SystemUI/res/layout/window_magnifier_view.xml
index 25d63e3175d0..6ced97836358 100644
--- a/packages/SystemUI/res/layout/window_magnifier_view.xml
+++ b/packages/SystemUI/res/layout/window_magnifier_view.xml
@@ -23,13 +23,13 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/magnification_outer_border_margin"
- android:background="@android:color/black" />
+ 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:background="@color/magnification_border_color" />
+ android:background="@color/magnification_border_color"/>
<RelativeLayout
android:layout_width="match_parent"
@@ -40,32 +40,32 @@
android:id="@+id/left_handle"
android:layout_width="@dimen/magnification_border_drag_size"
android:layout_height="match_parent"
- android:layout_above="@+id/bottom_handle" />
+ android:layout_above="@+id/bottom_handle"/>
<View
android:id="@+id/top_handle"
android:layout_width="match_parent"
android:layout_height="@dimen/magnification_border_drag_size"
- android:layout_alignParentTop="true" />
+ android:layout_alignParentTop="true"/>
<View
android:id="@+id/right_handle"
android:layout_width="@dimen/magnification_border_drag_size"
android:layout_height="match_parent"
android:layout_above="@+id/bottom_handle"
- android:layout_alignParentEnd="true" />
+ android:layout_alignParentEnd="true"/>
<View
android:id="@+id/bottom_handle"
android:layout_width="match_parent"
android:layout_height="@dimen/magnification_border_drag_size"
- android:layout_alignParentBottom="true" />
+ android:layout_alignParentBottom="true"/>
<SurfaceView
android:id="@+id/surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_margin="@dimen/magnification_mirror_surface_margin" />
+ android:layout_margin="@dimen/magnification_mirror_surface_margin"/>
</RelativeLayout>
@@ -73,8 +73,9 @@
android:id="@+id/drag_handle"
android:layout_width="@dimen/magnification_drag_view_size"
android:layout_height="@dimen/magnification_drag_view_size"
+ android:layout_margin="@dimen/magnification_outer_border_margin"
android:layout_gravity="right|bottom"
android:scaleType="center"
- android:src="@drawable/ic_move" />
+ 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 4a55ce3f077b..556c39091cce 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Wys laeprioriteit-kennisgewingikone"</string>
<string name="other" msgid="429768510980739978">"Ander"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Posisie <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Dubbeltik om te wysig."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Dubbeltik om by te voeg."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Skuif <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Verwyder <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Voeg <xliff:g id="TILE_NAME">%1$s</xliff:g> by posisie <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Skuif <xliff:g id="TILE_NAME">%1$s</xliff:g> na posisie <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Hou aan/af-skakelaar in om nuwe kontroles te sien"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Voeg kontroles by"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Wysig kontroles"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Voeg uitvoere by"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Groep"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 toestel gekies"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> toestelle gekies"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 7de4f443240c..c8462976528c 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"አነስተኛ ቅድሚያ ያላቸው የማሳወቂያ አዶዎችን አሳይ"</string>
<string name="other" msgid="429768510980739978">"ሌላ"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"ቦታ <xliff:g id="POSITION">%1$d</xliff:g>፣ <xliff:g id="TILE_NAME">%2$s</xliff:g>። ለማርትዕ ሁለቴ መታ ያድርጉ።"</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>። ለማከል ሁለቴ መታ ያድርጉ።"</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"<xliff:g id="TILE_NAME">%1$s</xliff:g>ን ይውሰዱ"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"<xliff:g id="TILE_NAME">%1$s</xliff:g>ን ያስወግዱ"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"<xliff:g id="TILE_NAME">%1$s</xliff:g>ን ወደ አቀማመጥ <xliff:g id="POSITION">%2$d</xliff:g> አክል"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"<xliff:g id="TILE_NAME">%1$s</xliff:g>ን ወደ አቀማመጥ <xliff:g id="POSITION">%2$d</xliff:g> አንቀሳቅስ"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"አዲስ መቆጣጠሪያዎችን ለማየት የኃይል አዝራር ይያዙ"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"መቆጣጠሪያዎችን አክል"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"መቆጣጠሪያዎችን ያርትዑ"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"ውጽዓቶችን ያክሉ"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"ቡድን"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 መሣሪያ ተመርጧል"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> መሣሪያዎች ተመርጠዋል"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 9874d70a87dc..785f8feb551a 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -318,7 +318,7 @@
<string name="data_usage_disabled_dialog" msgid="7933201635215099780">"تم الوصول إلى حد البيانات الذي عيَّنته. لم يُعد بإمكانك استخدام بيانات الجوال.\n\nفي حالة الاستئناف، قد يتم تطبيق الرسوم لاستخدام البيانات."</string>
<string name="data_usage_disabled_dialog_enable" msgid="2796648546086408937">"استئناف"</string>
<string name="gps_notification_searching_text" msgid="231304732649348313">"‏جارٍ البحث عن GPS"</string>
- <string name="gps_notification_found_text" msgid="3145873880174658526">"‏تم تعيين الموقع بواسطة GPS"</string>
+ <string name="gps_notification_found_text" msgid="3145873880174658526">"‏تم ضبط الموقع بواسطة GPS"</string>
<string name="accessibility_location_active" msgid="2845747916764660369">"طلبات الموقع نشطة"</string>
<string name="accessibility_sensors_off_active" msgid="2619725434618911551">"أجهزة الاستشعار غير مفعّلة"</string>
<string name="accessibility_clear_all" msgid="970525598287244592">"محو جميع الإشعارات."</string>
@@ -697,7 +697,7 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"عناصر التحكم في إشعارات التشغيل"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"تشغيل"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"إيقاف"</string>
- <string name="power_notification_controls_description" msgid="1334963837572708952">"باستخدام عناصر التحكم في إشعار التشغيل، يمكنك تعيين مستوى الأهمية من 0 إلى 5 لإشعارات التطبيق. \n\n"<b>"المستوى 5"</b>" \n- العرض أعلى قائمة الإشعارات \n- يسمح بمقاطعة ملء الشاشة \n- الظهور الخاطف دائمًا \n\n"<b>"المستوى 4"</b>" \n- منع مقاطعة ملء الشاشة \n- الظهور الخاطف دائمًا \n\n"<b>"المستوى 3"</b>" \n- منع مقاطعة ملء الشاشة \n- عدم الظهور الخاطف أبدًا \n\n"<b>"المستوى 2"</b>" \n- منع مقاطعة ملء الشاشة \n- عدم الظهور الخاطف أبدًا \n- عدم إصدار أصوات واهتزاز \n\n"<b>"المستوى 1"</b>" \n- منع مقاطعة ملء الشاشة \n- عدم الظهور الخاطف أبدًا \n- عدم إصدار أصوات أو اهتزاز أبدًا \n- الإخفاء من شاشة القفل وشريط الحالة \n- العرض أسفل قائمة الإشعارات \n\n"<b>"المستوى 0"</b>" \n- حظر جميع الإشعارات من التطبيق"</string>
+ <string name="power_notification_controls_description" msgid="1334963837572708952">"باستخدام عناصر التحكم في إشعار التشغيل، يمكنك ضبط مستوى الأهمية من 0 إلى 5 لإشعارات التطبيق. \n\n"<b>"المستوى 5"</b>" \n- العرض أعلى قائمة الإشعارات \n- يسمح بمقاطعة ملء الشاشة \n- الظهور الخاطف دائمًا \n\n"<b>"المستوى 4"</b>" \n- منع مقاطعة ملء الشاشة \n- الظهور الخاطف دائمًا \n\n"<b>"المستوى 3"</b>" \n- منع مقاطعة ملء الشاشة \n- عدم الظهور الخاطف أبدًا \n\n"<b>"المستوى 2"</b>" \n- منع مقاطعة ملء الشاشة \n- عدم الظهور الخاطف أبدًا \n- عدم إصدار أصوات واهتزاز \n\n"<b>"المستوى 1"</b>" \n- منع مقاطعة ملء الشاشة \n- عدم الظهور الخاطف أبدًا \n- عدم إصدار أصوات أو اهتزاز أبدًا \n- الإخفاء من شاشة القفل وشريط الحالة \n- العرض أسفل قائمة الإشعارات \n\n"<b>"المستوى 0"</b>" \n- حظر جميع الإشعارات من التطبيق"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"الإشعارات"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"لن تتلقى هذه الإشعارات بعد الآن."</string>
<string name="notification_channel_minimized" msgid="6892672757877552959">"سيتم تصغير هذه الإشعارات."</string>
@@ -899,12 +899,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"إظهار رموز الإشعارات ذات الأولوية المنخفضة"</string>
<string name="other" msgid="429768510980739978">"غير ذلك"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"الموضع <xliff:g id="POSITION">%1$d</xliff:g>، <xliff:g id="TILE_NAME">%2$s</xliff:g>. انقر مرّتين للتعديل."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. انقر مرّتين للإضافة."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"نقل <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"إزالة <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"إضافة <xliff:g id="TILE_NAME">%1$s</xliff:g> إلى الموضع <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"نقل <xliff:g id="TILE_NAME">%1$s</xliff:g> إلى الموضع <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1091,4 +1099,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"اضغط مع الاستمرار على زر التشغيل لعرض عناصر التحكّم الجديدة."</string>
<string name="controls_menu_add" msgid="4447246119229920050">"إضافة عناصر تحكّم"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"تعديل عناصر التحكّم"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"إضافة مخرجات"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"مجموعة"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"تم اختيار جهاز واحد."</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"تم اختيار <xliff:g id="COUNT">%1$d</xliff:g> جهاز."</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 55799a86089a..059c4bca8181 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"কম গুৰুত্বপূৰ্ণ জাননীৰ আইকনসমূহ দেখুৱাওক"</string>
<string name="other" msgid="429768510980739978">"অন্যান্য"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"অৱস্থান <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>। সম্পাদনা কৰিবৰ বাবে দুবাৰ টিপক।"</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>। যোগ কৰিবলৈ দুবাৰ টিপক।"</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"<xliff:g id="TILE_NAME">%1$s</xliff:g> স্থানান্তৰ কৰক"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"<xliff:g id="TILE_NAME">%1$s</xliff:g>ক আঁতৰাওক"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"এই স্থান <xliff:g id="POSITION">%2$d</xliff:g>ত <xliff:g id="TILE_NAME">%1$s</xliff:g> যোগ কৰক"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"<xliff:g id="TILE_NAME">%1$s</xliff:g>ক এই স্থান <xliff:g id="POSITION">%2$d</xliff:g>লৈ যাওক"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"নতুন নিয়ন্ত্ৰণসমূহ চাবলৈ পাৱাৰৰ বুটামটো ধৰি ৰাখক"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"নিয়ন্ত্ৰণসমূহ যোগ দিয়ক"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"নিয়ন্ত্ৰণসমূহ সম্পাদনা কৰক"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"আউটপুটসমূহ যোগ দিয়ক"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"গোট"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"১ টা ডিভাইচ বাছনি কৰা হৈছে"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> টা ডিভাইচ বাছনি কৰা হৈছে"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 83af4623b54c..2381d3330730 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -879,12 +879,20 @@
</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>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"<xliff:g id="POSITION">%1$d</xliff:g> mövqeyi, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Redaktə etmək üçün iki dəfə tıklayın."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Əlavə etmək üçün iki dəfə tıklayın."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"<xliff:g id="TILE_NAME">%1$s</xliff:g> köçürün"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"<xliff:g id="TILE_NAME">%1$s</xliff:g> silin"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"<xliff:g id="POSITION">%2$d</xliff:g> pozisiyasına <xliff:g id="TILE_NAME">%1$s</xliff:g> əlavə edin"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"<xliff:g id="POSITION">%2$d</xliff:g> pozisiyasına <xliff:g id="TILE_NAME">%1$s</xliff:g> köçürün"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Yeni nizamlayıcıları görmək üçün yandırıb-söndürmə düyməsinə basıb saxlayın"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Nizamlayıcılar əlavə edin"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Nizamlayıcıları redaktə edin"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Nəticələri əlavə edin"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Qrup"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 cihaz seçilib"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> cihaz seçilib"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index ce7b6077129f..55abfda98bc8 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -884,12 +884,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Prikaži ikone obaveštenja niskog prioriteta"</string>
<string name="other" msgid="429768510980739978">"Drugo"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"<xliff:g id="POSITION">%1$d</xliff:g>. pozicija, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Dvaput dodirnite da biste izmenili."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Dvaput dodirnite da biste dodali."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Premesti pločicu <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Ukloni pločicu <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Dodajte „<xliff:g id="TILE_NAME">%1$s</xliff:g>“ na poziciju <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Premestite „<xliff:g id="TILE_NAME">%1$s</xliff:g>“ na poziciju <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1073,4 +1081,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Zadržite dugme za uključivanje da biste videli nove kontrole"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Dodaj kontrole"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Izmeni kontrole"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Dodajte izlaze"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupa"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Izabran je 1 uređaj"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Izabranih uređaja: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index f1fc8f565a3b..47f9861f640b 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -722,7 +722,7 @@
<string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"У залежнасці ад налад тэлефона магчымы званок або вібрацыя. Размовы ў праграме \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" стандартна паяўляюцца ў выглядзе ўсплывальных апавяшчэнняў."</string>
<string name="notification_channel_summary_bubble" msgid="7235935211580860537">"Прыцягвае ўвагу да гэтага змесціва ўсплывальнай кнопкай."</string>
<string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Сістэма сама будзе вызначаць, ці трэба для гэтага апавяшчэння ўключаць гук або вібрацыю"</string>
- <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Паказваецца ўверсе раздзела размоў у выглядзе ўсплывальнага апавяшчэння, а на экране блакіроўкі – у выглядзе відарыса профілю"</string>
+ <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Паказваецца ўверсе раздзела размоў, як усплывальнае апавяшчэнне, паказвае фота профілю на экране блакіроўкі"</string>
<string name="notification_conversation_channel_settings" msgid="2409977688430606835">"Налады"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Прыярытэт"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> не падтрымлівае функцыі размовы"</string>
@@ -889,12 +889,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Паказваць значкі апавяшчэнняў з нізкім прыярытэтам"</string>
<string name="other" msgid="429768510980739978">"Іншае"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Месца: <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Краніце двойчы, каб рэдагаваць."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Краніце двойчы, каб дадаць."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Перамясціць <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Выдаліць <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Дадаць \"<xliff:g id="TILE_NAME">%1$s</xliff:g>\" у наступнае месца: <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Перамясціць \"<xliff:g id="TILE_NAME">%1$s</xliff:g>\" у наступнае месца: <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1079,4 +1087,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Каб убачыць новыя элементы кіравання, утрымлівайце кнопку сілкавання націснутай"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Дадаць элементы кіравання"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Змяніць элементы кіравання"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Дадайце прылады вываду"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Група"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Выбрана 1 прылада"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Выбрана прылад: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 1678abadfeff..efe12673fe78 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -716,7 +716,7 @@
<string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Може да звъни или да вибрира въз основа на настройките за телефона. Разговорите от <xliff:g id="APP_NAME">%1$s</xliff:g> се показват като балончета по подразбиране."</string>
<string name="notification_channel_summary_bubble" msgid="7235935211580860537">"Задържа вниманието ви посредством плаващ пряк път към това съдържание."</string>
<string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Нека системата да определя дали дадено известие да се придружава от звук, или вибриране"</string>
- <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Показва се като плаващо балонче в горната част на секцията с разговори и показва снимката на потребителския профил на заключения екран"</string>
+ <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Показва се като плаващо балонче в горната част на секцията с разговори, показва снимката на потр. профил на заключения екран"</string>
<string name="notification_conversation_channel_settings" msgid="2409977688430606835">"Настройки"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Приоритет"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> не поддържа функциите за разговор"</string>
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Показване на иконите за известията с нисък приоритет"</string>
<string name="other" msgid="429768510980739978">"Друго"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Позиция <xliff:g id="POSITION">%1$d</xliff:g>, „<xliff:g id="TILE_NAME">%2$s</xliff:g>“. Докоснете двукратно, за да редактирате."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"„<xliff:g id="TILE_NAME">%1$s</xliff:g>“. Докоснете двукратно, за да добавите."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Преместване на „<xliff:g id="TILE_NAME">%1$s</xliff:g>“"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Премахване на „<xliff:g id="TILE_NAME">%1$s</xliff:g>“"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Добавете „<xliff:g id="TILE_NAME">%1$s</xliff:g>“ на позиция <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Преместете „<xliff:g id="TILE_NAME">%1$s</xliff:g>“ на позиция <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Задръжте бутона за захранване, за да видите новите контроли"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Добавяне на контроли"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Редактиране на контролите"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Добавяне на изходящи устройства"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Група"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 избрано устройство"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> избрани устройства"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 9d5415fe5af1..fcdd286b6f55 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"কম-গুরুত্বপূর্ণ বিজ্ঞপ্তির আইকন দেখুন"</string>
<string name="other" msgid="429768510980739978">"অন্যান্য"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"<xliff:g id="POSITION">%1$d</xliff:g> লোকেশন, <xliff:g id="TILE_NAME">%2$s</xliff:g>৷ সম্পাদনা করতে দুবার আলতো চাপুন৷"</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>৷ যোগ করতে দুবার আলতো চাপুন৷"</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"<xliff:g id="TILE_NAME">%1$s</xliff:g> সরান"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"<xliff:g id="TILE_NAME">%1$s</xliff:g> সরান"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"<xliff:g id="TILE_NAME">%1$s</xliff:g> <xliff:g id="POSITION">%2$d</xliff:g>-এ যোগ করুন"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"<xliff:g id="TILE_NAME">%1$s</xliff:g> <xliff:g id="POSITION">%2$d</xliff:g>-এ সরান"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"নতুন কন্ট্রোল দেখতে পাওয়ার বোতাম টিপে ধরে থাকুন"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"কন্ট্রোল যোগ করুন"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"কন্ট্রোল এডিট করুন"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"আউটপুট যোগ করুন"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"গ্রুপ"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"১টি ডিভাইস বেছে নেওয়া হয়েছে"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g>টি ডিভাইস বেছে নেওয়া হয়েছে"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 819bfcf58674..811a971ab02b 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -884,12 +884,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Prikaži ikone obavještenja niskog prioriteta"</string>
<string name="other" msgid="429768510980739978">"Ostalo"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Pozicija <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Dodirnite dvaput za uređivanje."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g> Dodirnite dvaput za dodavanje."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Pomjeri <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Ukloni <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Dodaj <xliff:g id="TILE_NAME">%1$s</xliff:g> na poziciju <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Premjesti <xliff:g id="TILE_NAME">%1$s</xliff:g> na poziciju <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1073,4 +1081,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Zadržite dugme za uključivanje da vidite nove kontrole"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Dodaj kontrole"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Uredi kontrole"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Dodajte izlaze"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupa"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Odabran je 1 uređaj"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Broj odabranih uređaja: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index a7aff9850a6b..793c44b4544b 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -879,12 +879,20 @@
</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>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Posició <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Fes doble toc per editar-la."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Fes doble toc per afegir-ho."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Mou <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Suprimeix <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Afegeix <xliff:g id="TILE_NAME">%1$s</xliff:g> a la posició <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Mou <xliff:g id="TILE_NAME">%1$s</xliff:g> a la posició <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Mantén premut el botó d\'engegada per veure controls nous"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Afegeix controls"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Edita els controls"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Afegeix sortides"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grup"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositiu seleccionat"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"S\'han seleccionat <xliff:g id="COUNT">%1$d</xliff:g> dispositius"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 44864f0e8141..3f3d6be63f9c 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -722,7 +722,7 @@
<string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Může vyzvánět nebo vibrovat v závislosti na nastavení telefonu. Konverzace z aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> ve výchozím nastavení bublají."</string>
<string name="notification_channel_summary_bubble" msgid="7235935211580860537">"Přitahuje pozornost pomocí plovoucí zkratky k tomuto obsahu."</string>
<string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Nechat systém rozhodnout, zda má toto oznámení vydat zvuk či zavibrovat"</string>
- <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Zobrazuje se v horní části sekce konverzace a má podobu plovoucí bubliny, zobrazuje profilovou fotku na obrazovce uzamčení"</string>
+ <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Zobrazuje se v horní části sekce konverzací a má podobu plovoucí bubliny, zobrazuje profilovou fotku na obrazovce uzamčení"</string>
<string name="notification_conversation_channel_settings" msgid="2409977688430606835">"Nastavení"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Priorita"</string>
<string name="no_shortcut" msgid="8257177117568230126">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> funkce konverzace nepodporuje"</string>
@@ -889,12 +889,20 @@
</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>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Pozice <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Dvojitým klepnutím ji upravíte."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Dlaždici přidáte dvojitým klepnutím."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Přesunout dlaždici <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Odstranit dlaždici <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Přidat dlaždici <xliff:g id="TILE_NAME">%1$s</xliff:g> na pozici <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Přesunout dlaždici <xliff:g id="TILE_NAME">%1$s</xliff:g> na pozici <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1013,7 +1021,7 @@
<string name="inattentive_sleep_warning_title" msgid="3891371591713990373">"Pohotovostní režim"</string>
<string name="priority_onboarding_title" msgid="2893070698479227616">"Konverzace byla nastavena jako prioritní"</string>
<string name="priority_onboarding_behavior" msgid="5342816047020432929">"Chování prioritních konverzací:"</string>
- <string name="priority_onboarding_show_at_top_text" msgid="1678400241025513541">"Zobrazovat v horní části sekce konverzace"</string>
+ <string name="priority_onboarding_show_at_top_text" msgid="1678400241025513541">"Zobrazovat v horní části sekce konverzací"</string>
<string name="priority_onboarding_show_avatar_text" msgid="5756291381124091508">"Zobrazovat profilovou fotku na zámku obrazovky"</string>
<string name="priority_onboarding_appear_as_bubble_text" msgid="4227039772250263122">"Zobrazuje se jako plovoucí bublina nad aplikacemi"</string>
<string name="priority_onboarding_ignores_dnd_text" msgid="2918952762719600529">"Přerušit režim Nerušit"</string>
@@ -1079,4 +1087,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Nové ovládací prvky zobrazíte podržením vypínače"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Přidat ovládací prvky"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Upravit ovládací prvky"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Přidání výstupů"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Skupina"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Je vybráno 1 zařízení"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Vybraná zařízení: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index b6702126323d..1d9238932283 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -716,7 +716,7 @@
<string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Kan ringe eller vibrere baseret på telefonens indstillinger. Samtaler fra <xliff:g id="APP_NAME">%1$s</xliff:g> vises som standard i bobler."</string>
<string name="notification_channel_summary_bubble" msgid="7235935211580860537">"Fastholder din opmærksomhed med en svævende genvej til indholdet."</string>
<string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Få systemet til at afgøre, om denne notifikation skal vibrere eller afspille en lyd"</string>
- <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Vises øverst i samtalesektionen, som en svævende boble og med profilbillede på låseskærmen"</string>
+ <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Vises øverst i samtalesektionen som en svævende boble og med profilbillede på låseskærmen"</string>
<string name="notification_conversation_channel_settings" msgid="2409977688430606835">"Indstillinger"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prioritet"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> understøtter ikke samtalefunktioner"</string>
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Vis ikoner for notifikationer med lav prioritet"</string>
<string name="other" msgid="429768510980739978">"Andet"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Position <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Tryk to gange for at redigere."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Tryk to gange for at tilføje."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Flyt <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Fjern <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Føj <xliff:g id="TILE_NAME">%1$s</xliff:g> til position <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Flyt <xliff:g id="TILE_NAME">%1$s</xliff:g> til position <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Hold afbryderknappen nede for at se nye betjeningselementer"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Tilføj styring"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Rediger styring"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Tilføj medieudgange"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Gruppe"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Der er valgt 1 enhed"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Der er valgt <xliff:g id="COUNT">%1$d</xliff:g> enhed"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index af8d22ea4cd0..5976a1c95362 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -879,12 +879,20 @@
</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>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Position <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Zum Bearbeiten doppeltippen."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Zum Hinzufügen doppeltippen."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"<xliff:g id="TILE_NAME">%1$s</xliff:g> verschieben"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"<xliff:g id="TILE_NAME">%1$s</xliff:g> entfernen"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"<xliff:g id="TILE_NAME">%1$s</xliff:g> auf Position <xliff:g id="POSITION">%2$d</xliff:g> hinzufügen"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"<xliff:g id="TILE_NAME">%1$s</xliff:g> auf Position <xliff:g id="POSITION">%2$d</xliff:g> verschieben"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Zum Anzeigen der Karten für neue Geräte Ein-/Aus-Taste gedrückt halten"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Steuerelemente hinzufügen"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Steuerelemente bearbeiten"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Ausgabegeräte hinzufügen"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Gruppe"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Ein Gerät ausgewählt"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> Geräte ausgewählt"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 951f01d54bdf..1eae5d4a12f9 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -716,7 +716,7 @@
<string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Ενδέχεται να κουδουνίζει ή να δονείται βάσει των ρυθμίσεων του τηλεφώνου. Οι συζητήσεις από την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> εμφανίζονται σε συννεφάκι από προεπιλογή."</string>
<string name="notification_channel_summary_bubble" msgid="7235935211580860537">"Κρατάει την προσοχή σας με μια κινούμενη συντόμευση προς αυτό το περιεχόμενο."</string>
<string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Επιτρέψτε στο σύστημα να αποφασίσει αν αυτή η ειδοποίηση θα αναπαράγει έναν ήχο ή θα ενεργοποιήσει τη δόνηση της συσκευής"</string>
- <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Εμφανίζεται στο επάνω μέρος της ενότητας συζητήσεων, προβάλλεται ως κινούμενο συννεφάκι, εμφανίζει τη φωτογραφία προφίλ στην οθόνη κλειδώματος"</string>
+ <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Εμφανίζεται επάνω στις συζητήσεις, προβάλλεται ως κιν. συννεφάκι, εμφανίζει τη φωτ. προφίλ στην οθ. κλειδ."</string>
<string name="notification_conversation_channel_settings" msgid="2409977688430606835">"Ρυθμίσεις"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Προτεραιότητα"</string>
<string name="no_shortcut" msgid="8257177117568230126">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> δεν υποστηρίζει τις λειτουργίες συζήτησης"</string>
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Εμφάνιση εικονιδίων ειδοποιήσεων χαμηλής προτεραιότητας"</string>
<string name="other" msgid="429768510980739978">"Άλλο"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Θέση <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Πατήστε δύο φορές για επεξεργασία."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Πατήστε δύο φορές για προσθήκη."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Μετακίνηση <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Κατάργηση <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Προσθήκη <xliff:g id="TILE_NAME">%1$s</xliff:g> στη θέση <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Μετακίνηση <xliff:g id="TILE_NAME">%1$s</xliff:g> στη θέση <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Πατήστε το κουμπί λειτουργίας για να δείτε νέα στοιχεία ελέγχου."</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Προσθήκη στοιχείων ελέγχου"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Επεξεργασία στοιχείων ελέγχου"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Προσθήκη εξόδων"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Ομάδα"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Επιλέχτηκε 1 συσκευή"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Επιλέχτηκαν <xliff:g id="COUNT">%1$d</xliff:g> συσκευές"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index debbd834f98d..4ea0341f8c39 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -879,12 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Show low-priority notification icons"</string>
<string name="other" msgid="429768510980739978">"Other"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Position <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Double tap to edit."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Double tap to add."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Move <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Remove <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Add <xliff:g id="TILE_NAME">%1$s</xliff:g> to position <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Move <xliff:g id="TILE_NAME">%1$s</xliff:g> to position <xliff:g id="POSITION">%2$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"remove tile"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"add tile to end"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Move tile"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Add tile"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Move to <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Add to 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">"Quick settings editor."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> notification: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Open settings."</string>
@@ -1067,4 +1068,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Hold Power button to see new controls"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Add controls"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Edit controls"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Add outputs"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Group"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"One device selected"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> devices selected"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index b7ad5977e78f..3916e58ba076 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -879,12 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Show low-priority notification icons"</string>
<string name="other" msgid="429768510980739978">"Other"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Position <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Double tap to edit."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Double tap to add."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Move <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Remove <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Add <xliff:g id="TILE_NAME">%1$s</xliff:g> to position <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Move <xliff:g id="TILE_NAME">%1$s</xliff:g> to position <xliff:g id="POSITION">%2$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"remove tile"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"add tile to end"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Move tile"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Add tile"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Move to <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Add to 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">"Quick settings editor."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> notification: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Open settings."</string>
@@ -1067,4 +1068,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Hold Power button to see new controls"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Add controls"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Edit controls"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Add outputs"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Group"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"One device selected"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> devices selected"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index debbd834f98d..4ea0341f8c39 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -879,12 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Show low-priority notification icons"</string>
<string name="other" msgid="429768510980739978">"Other"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Position <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Double tap to edit."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Double tap to add."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Move <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Remove <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Add <xliff:g id="TILE_NAME">%1$s</xliff:g> to position <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Move <xliff:g id="TILE_NAME">%1$s</xliff:g> to position <xliff:g id="POSITION">%2$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"remove tile"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"add tile to end"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Move tile"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Add tile"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Move to <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Add to 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">"Quick settings editor."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> notification: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Open settings."</string>
@@ -1067,4 +1068,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Hold Power button to see new controls"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Add controls"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Edit controls"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Add outputs"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Group"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"One device selected"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> devices selected"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index debbd834f98d..4ea0341f8c39 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -879,12 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Show low-priority notification icons"</string>
<string name="other" msgid="429768510980739978">"Other"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Position <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Double tap to edit."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Double tap to add."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Move <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Remove <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Add <xliff:g id="TILE_NAME">%1$s</xliff:g> to position <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Move <xliff:g id="TILE_NAME">%1$s</xliff:g> to position <xliff:g id="POSITION">%2$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"remove tile"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"add tile to end"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Move tile"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Add tile"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Move to <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Add to 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">"Quick settings editor."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> notification: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Open settings."</string>
@@ -1067,4 +1068,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Hold Power button to see new controls"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Add controls"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Edit controls"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Add outputs"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Group"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"One device selected"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> devices selected"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index c76dc72714de..7702337f080f 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -879,12 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‏‏‎‎‏‎‏‎‎‏‎‏‏‎‏‏‎‎‎‎‏‎‎‏‏‏‎‎‏‏‏‎‎‎‏‏‏‎‎‎Show low-priority notification icons‎‏‎‎‏‎"</string>
<string name="other" msgid="429768510980739978">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‏‏‏‎‏‏‎‏‏‎‏‏‎‎‎‎‎‏‎‏‏‏‏‎‎‎‎‏‎‎‎‏‎‏‎‎‏‏‏‏‏‎‏‎‎‏‏‏‎‎‎‏‎‏‎‎Other‎‏‎‎‏‎"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‎‎‏‏‏‏‎‎‏‏‎‎‎‏‏‎‏‏‏‎‏‎‎‎‏‎‏‎‏‎‎‎‎‏‎‏‏‏‎‎‏‏‏‏‏‎‎‎‏‏‏‎Position ‎‏‎‎‏‏‎<xliff:g id="POSITION">%1$d</xliff:g>‎‏‎‎‏‏‏‎, ‎‏‎‎‏‏‎<xliff:g id="TILE_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎. Double tap to edit.‎‏‎‎‏‎"</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‏‎‎‏‏‏‏‏‎‎‎‎‎‎‏‏‎‎‏‏‏‎‎‎‎‏‎‎‎‏‎‎‎‏‏‏‏‎‎‏‎‏‎‏‎‎‏‎‎‎‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="TILE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎. Double tap to add.‎‏‎‎‏‎"</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‎‏‎‎‏‏‏‏‎‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‏‏‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‎‏‎‏‎‎‎‏‎‎Move ‎‏‎‎‏‏‎<xliff:g id="TILE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‏‎‏‎‎‎‏‏‏‎‏‎‏‎‎‏‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‏‏‏‎‏‎‎‎‎‎‏‎‎‏‏‎‏‏‎‏‎‎‎‎Remove ‎‏‎‎‏‏‎<xliff:g id="TILE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‎‎‏‎‏‎‎‎‏‎‎‏‎‎‎‎‏‏‎‎‎‎‎‏‏‏‎‏‏‏‎‏‎‎‏‎‎‎‎‏‏‏‏‎‎‎‎‏‏‎‎‎‎‏‎Add ‎‏‎‎‏‏‎<xliff:g id="TILE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ to position ‎‏‎‎‏‏‎<xliff:g id="POSITION">%2$d</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‎‏‏‎‎‎‏‎‏‏‎‏‏‎‎‏‎‏‏‏‏‎‏‏‏‏‎‎‏‎‎‏‏‎‏‏‏‎‏‎‏‏‎‎‎‏‏‏‎‏‏‎‏‎‏‎Move ‎‏‎‎‏‏‎<xliff:g id="TILE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ to position ‎‏‎‎‏‏‎<xliff:g id="POSITION">%2$d</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‏‏‎‎‎‎‏‏‎‎‏‎‏‎‏‏‏‏‏‏‎‏‎‎‏‏‎‏‏‎‎‏‎‏‏‎‎‏‎‏‏‎‏‏‎‏‏‎‎‎‎‎‏‎‎‎‎remove tile‎‏‎‎‏‎"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‎‎‏‏‎‎‏‏‎‎‎‎‎‏‎‎‏‏‏‏‎‎‎‎‏‎‏‎‎‎‏‎‏‎‏‏‎‏‎‏‎‎‏‏‏‏‏‎‏‎‎‏‎‎‏‎add tile to end‎‏‎‎‏‎"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‏‏‏‏‎‎‎‏‎‏‎‏‏‏‎‏‎‏‏‏‏‎‎‏‏‏‏‎‎‎‏‎‏‏‎‎‎‏‎‎‎‎‎‏‎‏‏‎‏‎‎‏‏‏‎‎‏‎Move tile‎‏‎‎‏‎"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‎‏‏‎‏‎‏‎‏‏‎‎‏‏‏‎‏‏‏‎‎‏‎‏‏‏‎‏‎‎‎‏‎‎‏‎‎‎‏‏‎‎‏‏‎‏‎‏‏‏‏‎‎‎Add tile‎‏‎‎‏‎"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‎‏‎‎‎‏‏‏‎‎‏‎‏‎‎‎‏‏‎‎‎‏‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‎‎‎‏‎‏‎‎‎‎‎‏‏‏‏‎‎‎Move to ‎‏‎‎‏‏‎<xliff:g id="POSITION">%1$d</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‏‏‎‎‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‎‏‏‎‎‎‎‏‎‎‏‎‎‏‎‎‎‎‎‎‎‎‏‎‎Add to 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">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‎‏‎‎‏‎‏‎‏‏‏‎‎‏‏‎‏‏‏‎‎‎‎‎‎‎‏‏‏‏‏‎‏‎‎‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‏‎‎‎‎‏‎Quick settings editor.‎‏‎‎‏‎"</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‏‏‏‎‏‏‏‏‎‎‎‏‏‏‎‎‎‏‏‏‎‎‎‎‎‎‏‏‏‏‏‎‎‎‎‏‏‏‏‏‏‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‎‎‏‏‎<xliff:g id="ID_1">%1$s</xliff:g>‎‏‎‎‏‏‏‎ notification: ‎‏‎‎‏‏‎<xliff:g id="ID_2">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‎‎‎‏‎‏‏‏‎‎‏‏‎‏‎‏‎‏‏‎‎‏‎‎‏‏‏‎‎‎‎‎‎‎‏‏‏‏‏‎‏‎‎‏‎‎‏‏‎‏‎‎‏‎Open settings.‎‏‎‎‏‎"</string>
@@ -1067,4 +1068,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‎‏‏‎‏‎‏‎‎‏‎‎‎‎‎‏‎‏‏‎‎‎‏‏‎‏‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‏‎‎‎‎‏‏‎‏‏‏‏‎‏‎Hold Power button to see new controls‎‏‎‎‏‎"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‏‏‎‏‏‏‏‏‎‎‏‎‏‎‏‏‎‎‏‎‎‎‎‏‏‎‏‏‎‏‎‏‏‎‏‏‏‏‎‎‎‏‎‎‏‏‎‎‏‏‎‎‏‎‎Add controls‎‏‎‎‏‎"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‏‏‏‎‎‏‏‎‎‎‎‏‎‎‎‏‎‏‎‏‎‏‏‎‏‎‏‎‏‏‎‏‏‎‏‏‏‎‏‏‎‎Edit controls‎‏‎‎‏‎"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‏‎‎‏‏‏‎‏‏‏‎‏‎‎‎‏‎‏‏‎‎‏‏‎‎‎‎‏‏‏‎‎‎‏‏‏‎‏‎‏‎‏‏‏‎‎‎‎‏‏‎‏‏‏‎‎Add outputs‎‏‎‎‏‎"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‏‎‏‎‏‎‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‏‎‏‎‎‏‎‎‎‏‎‏‎‎‏‏‎‏‏‎‏‏‎‎‏‏‎‏‏‎‏‎‎‎Group‎‏‎‎‏‎"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‎‎‎‎‏‏‏‏‎‎‏‏‎‏‏‎‏‏‎‏‏‎‏‎‏‎‎‏‎‎‎‎‏‏‏‎‎‏‏‎‎‏‏‎‏‎‏‎‎‎‎‏‎‎‏‎‎1 device selected‎‏‎‎‏‎"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‎‎‏‎‏‏‎‏‏‏‎‏‏‎‏‎‏‏‏‏‎‏‏‏‎‏‎‏‎‎‏‏‏‎‎‏‏‏‎‎‎‎‏‏‎‏‏‎‏‎‎‎‏‏‏‎‎‎‏‎‎‏‏‎<xliff:g id="COUNT">%1$d</xliff:g>‎‏‎‎‏‏‏‎ devices selected‎‏‎‎‏‎"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 1ef965d22748..b1ee41f4c53c 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -706,19 +706,19 @@
<string name="inline_silent_button_keep_alerting" msgid="6577845442184724992">"Seguir recibiendo alertas"</string>
<string name="inline_turn_off_notifications" msgid="8543989584403106071">"Desactivar notificaciones"</string>
<string name="inline_keep_showing_app" msgid="4393429060390649757">"¿Quieres seguir viendo las notificaciones de esta app?"</string>
- <string name="notification_silence_title" msgid="8608090968400832335">"Silencio"</string>
+ <string name="notification_silence_title" msgid="8608090968400832335">"Silenciada"</string>
<string name="notification_alert_title" msgid="3656229781017543655">"Predeterminada"</string>
<string name="notification_bubble_title" msgid="8330481035191903164">"Cuadro"</string>
<string name="notification_automatic_title" msgid="3745465364578762652">"Automática"</string>
<string name="notification_channel_summary_low" msgid="4860617986908931158">"Sin sonido ni vibración"</string>
- <string name="notification_conversation_summary_low" msgid="1734433426085468009">"No suena ni vibra, y aparece en una parte inferior de la sección de conversaciones"</string>
- <string name="notification_channel_summary_default" msgid="3282930979307248890">"Puede sonar o vibrar en función de la configuración del teléfono"</string>
+ <string name="notification_conversation_summary_low" msgid="1734433426085468009">"No suena ni vibra, y aparece en la parte inferior de la sección de conversaciones."</string>
+ <string name="notification_channel_summary_default" msgid="3282930979307248890">"Puede sonar o vibrar en función de la configuración del teléfono."</string>
<string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Puede sonar o vibrar en función de la configuración del teléfono. Conversaciones de la burbuja de <xliff:g id="APP_NAME">%1$s</xliff:g> de forma predeterminada."</string>
<string name="notification_channel_summary_bubble" msgid="7235935211580860537">"Retiene tu atención con un acceso directo flotante a este contenido."</string>
<string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Dejar que el sistema determine si esta notificación debe emitir un sonido o una vibración"</string>
- <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Aparece en la parte superior de la sección de conversaciones, en forma de burbuja flotante, y muestra la foto de perfil en la pantalla de bloqueo"</string>
+ <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Aparece en la parte superior de la sección de conversaciones, en forma de burbuja flotante, y muestra la foto de perfil en la pantalla de bloqueo."</string>
<string name="notification_conversation_channel_settings" msgid="2409977688430606835">"Configuración"</string>
- <string name="notification_priority_title" msgid="2079708866333537093">"Prioridad"</string>
+ <string name="notification_priority_title" msgid="2079708866333537093">"Prioritaria"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> no admite funciones de conversación"</string>
<string name="bubble_overflow_empty_title" msgid="3120029421991510842">"No hay burbujas recientes"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2030874469510497397">"Las burbujas recientes y las que se descartaron aparecerán aquí"</string>
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Mostrar íconos de notificaciones con prioridad baja"</string>
<string name="other" msgid="429768510980739978">"Otros"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Posición <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Presiona dos veces para editarla."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Presiona dos veces para agregarlo."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Mover <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Quitar <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Agregar <xliff:g id="TILE_NAME">%1$s</xliff:g> a la posición <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Mover <xliff:g id="TILE_NAME">%1$s</xliff:g> a la posición <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Mantén presionado el botón de encendido para ver los nuevos controles"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Agregar controles"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Editar controles"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Agregar salidas"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Se seleccionó 1 dispositivo"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Se seleccionaron <xliff:g id="COUNT">%1$d</xliff:g> dispositivos"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index e483c0e1ac6d..7e5361b4f70c 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -711,12 +711,12 @@
<string name="notification_bubble_title" msgid="8330481035191903164">"Burbuja"</string>
<string name="notification_automatic_title" msgid="3745465364578762652">"Automática"</string>
<string name="notification_channel_summary_low" msgid="4860617986908931158">"Sin sonido ni vibración"</string>
- <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Sin sonido ni vibración y se muestra más abajo en la sección de conversaciones"</string>
+ <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Sin sonido ni vibración, y se muestra más abajo en la sección de conversaciones"</string>
<string name="notification_channel_summary_default" msgid="3282930979307248890">"Es posible que suene o vibre según los ajustes del teléfono"</string>
<string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Es posible que suene o vibre según los ajustes del teléfono. Las conversaciones de <xliff:g id="APP_NAME">%1$s</xliff:g> aparecen como burbujas de forma predeterminada."</string>
<string name="notification_channel_summary_bubble" msgid="7235935211580860537">"Llama tu atención con un acceso directo flotante a este contenido."</string>
<string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Haz que el sistema determine si con esta notificación el dispositivo debe sonar o vibrar"</string>
- <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Se muestra arriba en la sección de conversaciones en forma de burbuja flotante, y la imagen de perfil aparece en la pantalla de bloqueo"</string>
+ <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Se muestra arriba en la sección de conversaciones, como burbuja flotante, y la imagen de perfil aparece en la pantalla de bloqueo"</string>
<string name="notification_conversation_channel_settings" msgid="2409977688430606835">"Ajustes"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prioridad"</string>
<string name="no_shortcut" msgid="8257177117568230126">"No se pueden usar funciones de conversación con <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Mostrar iconos de notificaciones con prioridad baja"</string>
<string name="other" msgid="429768510980739978">"Otros"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Posición <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Toca dos veces para cambiarla."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Toca dos veces para añadirlo."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Mover <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Quitar <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Añadir <xliff:g id="TILE_NAME">%1$s</xliff:g> a la posición <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Mover <xliff:g id="TILE_NAME">%1$s</xliff:g> a la posición <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Mantén pulsado el botón de encendido para ver los controles nuevos"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Añadir controles"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Editar controles"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Añadir dispositivos de salida"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositivo seleccionado"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> dispositivos seleccionados"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index fea0df62e625..78de793517f4 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Kuva madala prioriteediga märguande ikoonid"</string>
<string name="other" msgid="429768510980739978">"Muu"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Asend <xliff:g id="POSITION">%1$d</xliff:g>, paan <xliff:g id="TILE_NAME">%2$s</xliff:g>. Topeltpuudutage muutmiseks."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Topeltpuudutage lisamiseks."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Paani <xliff:g id="TILE_NAME">%1$s</xliff:g> teisaldamine"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Paani <xliff:g id="TILE_NAME">%1$s</xliff:g> eemaldamine"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Lisage <xliff:g id="TILE_NAME">%1$s</xliff:g> asendisse <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Teisaldage <xliff:g id="TILE_NAME">%1$s</xliff:g> asendisse <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Uute juhtelementide vaatamiseks hoidke all toitenuppu"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Lisa juhtelemente"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Muuda juhtelemente"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Väljundite lisamine"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupp"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 seade on valitud"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> seadet on valitud"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 297472a66325..b5d84447271a 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Erakutsi lehentasun txikiko jakinarazpenen ikonoak"</string>
<string name="other" msgid="429768510980739978">"Beste bat"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"<xliff:g id="POSITION">%1$d</xliff:g>. posizioa, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Editatzeko, sakatu birritan."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Gehitzeko, sakatu birritan."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Mugitu <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Kendu <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Gehitu <xliff:g id="TILE_NAME">%1$s</xliff:g> <xliff:g id="POSITION">%2$d</xliff:g>garren postuan"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Eraman <xliff:g id="TILE_NAME">%1$s</xliff:g> <xliff:g id="POSITION">%2$d</xliff:g>garren postura"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Eduki sakatuta etengailua kontrolatzeko aukera berriak ikusteko"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Gehitu aukerak"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Editatu aukerak"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Gehitu irteerak"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Taldea"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 gailu hautatu da"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> gailu hautatu dira"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 2c32225d5ec2..d453f91da504 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"نمایش نمادهای اعلان کم‌اهمیت"</string>
<string name="other" msgid="429768510980739978">"موارد دیگر"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"موقعیت <xliff:g id="POSITION">%1$d</xliff:g>، <xliff:g id="TILE_NAME">%2$s</xliff:g>. برای ویرایش دو ضربه سریع بزنید."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. برای افزودن دو ضربه سریع بزنید."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"انتقال <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"حذف <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"افزودن <xliff:g id="TILE_NAME">%1$s</xliff:g> به موقعیت <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"انتقال <xliff:g id="TILE_NAME">%1$s</xliff:g> به موقعیت <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"برای دیدن کنترل‌های جدید، دکمه روشن/خاموش را پایین نگه دارید"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"افزودن کنترل‌ها"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"ویرایش کنترل‌ها"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"افزودن خروجی"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"گروه"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"۱ دستگاه انتخاب شد"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> دستگاه انتخاب شد"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index b6c4ba0c2578..dde2449d8894 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -879,12 +879,20 @@
</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>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Paikka <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Muokkaa kaksoisnapauttamalla."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Lisää kaksoisnapauttamalla."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Siirrä <xliff:g id="TILE_NAME">%1$s</xliff:g>."</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Poista <xliff:g id="TILE_NAME">%1$s</xliff:g>."</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Siirrä <xliff:g id="TILE_NAME">%1$s</xliff:g> kohtaan <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Siirrä <xliff:g id="TILE_NAME">%1$s</xliff:g> kohtaan <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Paina virtapainiketta pitkään nähdäksesi uudet säätimet"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Lisää säätimiä"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Muokkaa säätimiä"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Lisää toistotapoja"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Ryhmä"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 laite valittu"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> laitetta valittu"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 34391efe460d..ea6de66db50f 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -879,12 +879,20 @@
</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>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Position <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Touchez deux fois pour modifier."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Touchez deux fois pour ajouter."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Déplacer <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Supprimer <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Ajouter <xliff:g id="TILE_NAME">%1$s</xliff:g> à la position <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Déplacer <xliff:g id="TILE_NAME">%1$s</xliff:g> à la position <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Maintenez enfoncé l\'interrupteur pour afficher les nouvelles commandes"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Ajouter des commandes"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Modifier des commandes"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Ajouter des sorties"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Groupe"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Un appareil sélectionné"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> appareil sélectionné"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index ebd9b9b9f40c..fc2691e3570d 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -879,12 +879,20 @@
</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>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Position <xliff:g id="POSITION">%1$d</xliff:g>, \"<xliff:g id="TILE_NAME">%2$s</xliff:g>\". Appuyer deux fois pour modifier."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Appuyer deux fois pour ajouter."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Déplacer \"<xliff:g id="TILE_NAME">%1$s</xliff:g>\""</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Supprimer \"<xliff:g id="TILE_NAME">%1$s</xliff:g>\""</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Ajouter l\'élément \"<xliff:g id="TILE_NAME">%1$s</xliff:g>\" à la position <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Déplacer l\'élément \"<xliff:g id="TILE_NAME">%1$s</xliff:g>\" à la position <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Appuyez de manière prolongée sur le bouton Marche/Arrêt pour afficher les nouvelles commandes"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Ajouter des commandes"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Modifier des commandes"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Ajouter des sorties"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Groupe"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 appareil sélectionné"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> appareils sélectionnés"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 79795927c1af..aba2d8fd389a 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -711,12 +711,12 @@
<string name="notification_bubble_title" msgid="8330481035191903164">"Burbulla"</string>
<string name="notification_automatic_title" msgid="3745465364578762652">"Automática"</string>
<string name="notification_channel_summary_low" msgid="4860617986908931158">"Sen son nin vibración"</string>
- <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Non soa nin vibra, e aparece máis abaixo na sección de conversas"</string>
- <string name="notification_channel_summary_default" msgid="3282930979307248890">"Podería soar ou vibrar en función da configuración do teléfono"</string>
+ <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Sen son nin vibración, e aparecen máis abaixo na sección de conversas"</string>
+ <string name="notification_channel_summary_default" msgid="3282930979307248890">"Poderían soar ou vibrar en función da configuración do teléfono"</string>
<string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Podería soar ou vibrar en función da configuración do teléfono. Conversas desde a burbulla da aplicación <xliff:g id="APP_NAME">%1$s</xliff:g> de forma predeterminada."</string>
<string name="notification_channel_summary_bubble" msgid="7235935211580860537">"Mantén a túa atención cun atallo flotante a este contido."</string>
<string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Fai que o sistema determine se a notificación debe emitir un son ou unha vibración"</string>
- <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Móstrase na parte superior da sección de conversas en forma de burbulla flotante e aparece a imaxe do perfil na pantalla de bloqueo"</string>
+ <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Móstranse na parte superior da sección de conversas en forma de burbulla flotante e aparece a imaxe do perfil na pantalla de bloqueo"</string>
<string name="notification_conversation_channel_settings" msgid="2409977688430606835">"Configuración"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prioridade"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> non admite funcións de conversa"</string>
@@ -879,12 +879,20 @@
</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>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Posición <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Toca dúas veces o elemento para editalo."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Toca dúas veces o elemento para engadilo"</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Move <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Elimina <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Engadir \"<xliff:g id="TILE_NAME">%1$s</xliff:g>\" á posición <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Mover \"<xliff:g id="TILE_NAME">%1$s</xliff:g>\" á posición <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Mantén premido o botón de acendido para ver os novos controis"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Engadir controis"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Editar controis"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Engadir saídas"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Seleccionouse 1 dispositivo"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Seleccionáronse <xliff:g id="COUNT">%1$d</xliff:g> dispositivos"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 9599ca868365..1acef350034a 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/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,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"ઓછી પ્રાધાન્યતાનું નોટિફિકેશન આઇકન બતાવો"</string>
<string name="other" msgid="429768510980739978">"અન્ય"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"સ્થિતિ <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. સંપાદિત કરવા માટે બે વાર ટૅપ કરો."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. ઉમેરવા માટે બે વાર ટૅપ કરો."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"<xliff:g id="TILE_NAME">%1$s</xliff:g> ખસેડો"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"<xliff:g id="TILE_NAME">%1$s</xliff:g> દૂર કરો"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"<xliff:g id="POSITION">%2$d</xliff:g> જગ્યા પર <xliff:g id="TILE_NAME">%1$s</xliff:g>ને ઉમેરો"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"<xliff:g id="POSITION">%2$d</xliff:g> જગ્યા પર <xliff:g id="TILE_NAME">%1$s</xliff:g>ને ખસેડો"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"નવા નિયંત્રણ જોવા માટે પાવર બટનને દબાવી રાખો"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"નિયંત્રણો ઉમેરો"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"નિયંત્રણોમાં ફેરફાર કરો"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"આઉટપુટ ઉમેરો"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"ગ્રૂપ"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 ડિવાઇસ પસંદ કર્યું"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> ડિવાઇસ પસંદ કર્યા"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index f3a7ec1c0cca..1a69e81a8191 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -512,7 +512,7 @@
<string name="manage_notifications_history_text" msgid="57055985396576230">"इतिहास"</string>
<string name="notification_section_header_incoming" msgid="850925217908095197">"नई सूचनाएं"</string>
<string name="notification_section_header_gentle" msgid="6804099527336337197">"बिना आवाज़ किए मिलने वाली सूचनाएं"</string>
- <string name="notification_section_header_alerting" msgid="5581175033680477651">"वाइब्रेशन या आवाज़ के साथ मिलने वाली सूचनाएं"</string>
+ <string name="notification_section_header_alerting" msgid="5581175033680477651">"सूचनाएं"</string>
<string name="notification_section_header_conversations" msgid="821834744538345661">"बातचीत"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"बिना आवाज़ की सभी सूचनाएं हटाएं"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"\'परेशान न करें\' सुविधा के ज़रिए कुछ समय के लिए सूचनाएं दिखाना रोक दिया गया है"</string>
@@ -713,12 +713,12 @@
<string name="notification_bubble_title" msgid="8330481035191903164">"बबल"</string>
<string name="notification_automatic_title" msgid="3745465364578762652">"अपने-आप"</string>
<string name="notification_channel_summary_low" msgid="4860617986908931158">"किसी तरह की आवाज़ या वाइब्रेशन न हो"</string>
- <string name="notification_conversation_summary_low" msgid="1734433426085468009">"इससे किसी तरह की आवाज़ या वाइब्रेशन नहीं होता और \'बातचीत\', सेक्शन में सबसे नीचे दिखती है"</string>
+ <string name="notification_conversation_summary_low" msgid="1734433426085468009">"इससे किसी तरह की आवाज़ या वाइब्रेशन नहीं होता और बातचीत, सेक्शन में सबसे नीचे दिखती है"</string>
<string name="notification_channel_summary_default" msgid="3282930979307248890">"फ़ोन की सेटिंग के आधार पर, सूचना आने पर घंटी बज सकती है या वाइब्रेशन हो सकता है"</string>
<string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"फ़ोन की सेटिंग के आधार पर, सूचना आने पर घंटी बज सकती है या वाइब्रेशन हो सकता है. <xliff:g id="APP_NAME">%1$s</xliff:g> में होने वाली बातचीत, डिफ़ॉल्ट रूप से बबल के तौर पर दिखती है."</string>
<string name="notification_channel_summary_bubble" msgid="7235935211580860537">"फ़्लोट करने वाले शॉर्टकट की मदद से इस सामग्री पर आपका ध्यान बना रहता है."</string>
<string name="notification_channel_summary_automatic" msgid="5813109268050235275">"सिस्टम को यह तय करने की अनुमति दें कि इस सूचना के मिलने पर आवाज़ हो या वाइब्रेशन हो"</string>
- <string name="notification_channel_summary_priority" msgid="7952654515769021553">"इससे बातचीत की सुविधा, सेक्शन में सबसे ऊपर और फ़्लोटिंग बबल के तौर पर दिखती है. साथ ही, लॉक स्क्रीन पर प्रोफ़ाइल फ़ोटो दिखती है"</string>
+ <string name="notification_channel_summary_priority" msgid="7952654515769021553">"इससे बातचीत, सेक्शन में सबसे ऊपर और फ़्लोटिंग बबल के तौर पर दिखती है. साथ ही, लॉक स्क्रीन पर प्रोफ़ाइल फ़ोटो दिखती है"</string>
<string name="notification_conversation_channel_settings" msgid="2409977688430606835">"सेटिंग"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"प्राथमिकता"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> पर बातचीत की सुविधाएं काम नहीं करतीं"</string>
@@ -881,12 +881,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"कम प्राथमिकता वाली सूचना के आइकॉन दिखाएं"</string>
<string name="other" msgid="429768510980739978">"अन्य"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"स्थिति <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. में बदलाव करने के लिए दो बार छूएं."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. जोड़ने के लिए दो बार छूएं."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"<xliff:g id="TILE_NAME">%1$s</xliff:g> को ले जाएं"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"<xliff:g id="TILE_NAME">%1$s</xliff:g> निकालें"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"<xliff:g id="TILE_NAME">%1$s</xliff:g> काे क्रम संख्या <xliff:g id="POSITION">%2$d</xliff:g> पर जाेड़ें"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"<xliff:g id="TILE_NAME">%1$s</xliff:g> काे क्रम संख्या <xliff:g id="POSITION">%2$d</xliff:g> पर ले जाएं"</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_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>
@@ -1069,4 +1077,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"नए कंट्रोल देखने के लिए पावर बटन दबाकर रखें"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"कंट्राेल जोड़ें"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"कंट्रोल मेन्यू में बदलाव करें"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"आउटपुट जोड़ें"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"ग्रुप"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"एक डिवाइस चुना गया"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> डिवाइस चुने गए"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 870ca49add60..9b3f99748375 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -884,12 +884,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Prikaži ikone obavijesti niskog prioriteta"</string>
<string name="other" msgid="429768510980739978">"Ostalo"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Položaj <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Dodirnite dvaput da biste uredili."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Dodirnite dvaput da biste dodali."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Premjesti <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Ukloni <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Dodajte pločicu <xliff:g id="TILE_NAME">%1$s</xliff:g> na položaj <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Premjestite pločicu <xliff:g id="TILE_NAME">%1$s</xliff:g> na položaj <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1073,4 +1081,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Zadržite tipku za uključivanje/isključivanje za prikaz novih kontrola"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Dodaj kontrole"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Uredi kontrole"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Dodavanje izlaza"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupa"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Odabran je jedan uređaj"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Odabrano uređaja: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index f88076f54b22..7f4fa9d0acd3 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -879,12 +879,20 @@
</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>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"<xliff:g id="POSITION">%1$d</xliff:g>. pozíció: <xliff:g id="TILE_NAME">%2$s</xliff:g>. Koppintson duplán a szerkesztéshez."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Koppintson duplán a hozzáadáshoz."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"A(z) <xliff:g id="TILE_NAME">%1$s</xliff:g> áthelyezése"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"A(z) <xliff:g id="TILE_NAME">%1$s</xliff:g> eltávolítása"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"<xliff:g id="TILE_NAME">%1$s</xliff:g> hozzáadása a következő pozícióhoz: <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"<xliff:g id="TILE_NAME">%1$s</xliff:g> áthelyezése a következő pozícióba: <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Az új vezérlők megtekintéséhez tartsa nyomva a bekapcsológombot"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Vezérlők hozzáadása"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Vezérlők szerkesztése"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Kimenetek hozzáadása"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Csoport"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 eszköz kiválasztva"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> eszköz kiválasztva"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 04c2bb1a5237..70cbf493feca 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -511,7 +511,7 @@
<string name="notification_section_header_incoming" msgid="850925217908095197">"Նոր"</string>
<string name="notification_section_header_gentle" msgid="6804099527336337197">"Անձայն"</string>
<string name="notification_section_header_alerting" msgid="5581175033680477651">"Ծանուցումներ"</string>
- <string name="notification_section_header_conversations" msgid="821834744538345661">"Խոսակցություններ"</string>
+ <string name="notification_section_header_conversations" msgid="821834744538345661">"Զրույցներ"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Ջնջել բոլոր անձայն ծանուցումները"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Ծանուցումները չեն ցուցադրվի «Չանհանգստացնել» ռեժիմում"</string>
<string name="media_projection_action_text" msgid="3634906766918186440">"Սկսել հիմա"</string>
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Ցուցադրել ցածր առաջնահերթության ծանուցումների պատկերակները"</string>
<string name="other" msgid="429768510980739978">"Այլ"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Դիրք <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>: Կրկնակի հպեք՝ փոխելու համար:"</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>: Կրկնակի հպեք՝ ավելացնելու համար:"</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Տեղափոխել <xliff:g id="TILE_NAME">%1$s</xliff:g> սալիկը"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Հեռացնել <xliff:g id="TILE_NAME">%1$s</xliff:g> սալիկը"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Ավելացնել <xliff:g id="TILE_NAME">%1$s</xliff:g> սալիկը դիրք <xliff:g id="POSITION">%2$d</xliff:g>-ում"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"<xliff:g id="TILE_NAME">%1$s</xliff:g> սալիկը տեղափոխել դիրք <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Սեղմած պահեք սնուցման կոճակը՝ կառավարման նոր տարրերը տեսնելու համար։"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Ավելացնել կառավարման տարրեր"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Փոփոխել կառավարման տարրերը"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Ավելացրեք մուտքագրման սարքեր"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Խումբ"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Ընտրված է 1 սարք"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Ընտրված է <xliff:g id="COUNT">%1$d</xliff:g> սարք"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 403af01642dd..dc0a5128b808 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Tampilkan ikon notifikasi prioritas rendah"</string>
<string name="other" msgid="429768510980739978">"Lainnya"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Posisi <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Ketuk dua kali untuk mengedit."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Ketuk dua kali untuk menambahkan."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Pindahkan <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Hapus <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Tambahkan <xliff:g id="TILE_NAME">%1$s</xliff:g> ke posisi <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Pindahkan <xliff:g id="TILE_NAME">%1$s</xliff:g> ke posisi <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Tahan Tombol daya untuk melihat kontrol baru"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Tambahkan kontrol"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Edit kontrol"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Tambahkan output"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grup"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 perangkat dipilih"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> perangkat dipilih"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index a1c4a7f651c7..f79796b6276d 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -879,12 +879,20 @@
</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>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Staða <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Ýttu tvisvar til að breyta."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Ýttu tvisvar til að bæta við."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Færa <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Fjarlægja <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Bæta <xliff:g id="TILE_NAME">%1$s</xliff:g> við í stöðu <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Færa <xliff:g id="TILE_NAME">%1$s</xliff:g> í stöðu <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Haltu aflrofanum inni til að sjá nýjar stýringar"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Bæta við stýringum"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Breyta stýringum"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Bæta við úttaki"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Hópur"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 tæki valið"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> tæki valin"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index be017e11330a..3b99dc2e1f6e 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -716,7 +716,7 @@
<string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Può suonare o vibrare in base alle impostazioni del telefono. Conversazioni dalla bolla <xliff:g id="APP_NAME">%1$s</xliff:g> per impostazione predefinita."</string>
<string name="notification_channel_summary_bubble" msgid="7235935211580860537">"Mantiene la tua attenzione con una scorciatoia mobile a questi contenuti."</string>
<string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Fai stabilire al sistema se questa notifica deve emettere suoni o vibrazioni"</string>
- <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Viene mostrata in cima alla sezione delle conversazioni, appare sotto forma di bolla mobile, mostra l\'immagine del profilo nella schermata di blocco"</string>
+ <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Appare in cima alla sezione delle conversazioni e sotto forma di bolla mobile, mostra l\'immagine del profilo nella schermata di blocco"</string>
<string name="notification_conversation_channel_settings" msgid="2409977688430606835">"Impostazioni"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Priorità"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> non supporta le funzionalità delle conversazioni"</string>
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Mostra icone di notifiche con priorità bassa"</string>
<string name="other" msgid="429768510980739978">"Altro"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Posizione <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Tocca due volte per modificare."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Tocca due volte per aggiungere."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Sposta <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Rimuovi <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Aggiungi il riquadro <xliff:g id="TILE_NAME">%1$s</xliff:g> alla posizione <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Sposta il riquadro <xliff:g id="TILE_NAME">%1$s</xliff:g> nella posizione <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Tieni premuto il tasto di accensione per visualizzare i nuovi controlli"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Aggiungi controlli"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Modifica controlli"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Aggiungi uscite"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Gruppo"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositivo selezionato"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> dispositivi selezionati"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 1baa627c5a7d..3bb8ea5fe1dd 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -889,12 +889,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"הצגת סמלי התראות בעדיפות נמוכה"</string>
<string name="other" msgid="429768510980739978">"אחר"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"מיקום <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. הקש פעמיים כדי לערוך."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. הקש פעמיים כדי להוסיף."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"הזזת <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"הסרת <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"הוספת <xliff:g id="TILE_NAME">%1$s</xliff:g> למיקום <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"העברת <xliff:g id="TILE_NAME">%1$s</xliff:g> למיקום <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1079,4 +1087,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"ניתן ללחוץ על לחצן ההפעלה כדי להציג פקדים חדשים"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"הוספת פקדים"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"עריכת פקדים"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"הוספת מכשירי פלט"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"קבוצה"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"נבחר מכשיר אחד"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"נבחרו <xliff:g id="COUNT">%1$d</xliff:g> מכשירים"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index fd1aa57f39d9..2c55569bb96a 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"優先度の低い通知アイコンを表示"</string>
<string name="other" msgid="429768510980739978">"その他"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"ポジション <xliff:g id="POSITION">%1$d</xliff:g> の <xliff:g id="TILE_NAME">%2$s</xliff:g> を編集するにはダブルタップします。"</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g> を追加するにはダブルタップします。"</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"<xliff:g id="TILE_NAME">%1$s</xliff:g> を移動します"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"<xliff:g id="TILE_NAME">%1$s</xliff:g> を削除します"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"<xliff:g id="TILE_NAME">%1$s</xliff:g> をポジション <xliff:g id="POSITION">%2$d</xliff:g> に追加"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"<xliff:g id="TILE_NAME">%1$s</xliff:g> をポジション <xliff:g id="POSITION">%2$d</xliff:g> に移動"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"電源ボタンを長押しすると、新しいコントロールが表示されます"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"コントロールを追加"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"コントロールを編集"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"出力の追加"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"グループ"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"選択したデバイス: 1 台"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"選択したデバイス: <xliff:g id="COUNT">%1$d</xliff:g> 台"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 86451b8da7a5..ca5b7da19247 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -716,7 +716,7 @@
<string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"დარეკვა ან ვიბრაცია ტელეფონის პარამეტრების მიხედვით. მიმოწერები <xliff:g id="APP_NAME">%1$s</xliff:g>-ის ბუშტიდან, ნაგულისხმევად."</string>
<string name="notification_channel_summary_bubble" msgid="7235935211580860537">"იპყრობს თქვენს ყურადღებას ამ კონტენტის მოლივლივე მალსახმობით."</string>
<string name="notification_channel_summary_automatic" msgid="5813109268050235275">"სისტემისთვის ისეთი უფლების მინიჭება, რომ მან განსაზღვროს, ამ შეტყობინებამ ხმოვანი სიგნალი უნდა აამოქმედოს თუ ვიბრაცია"</string>
- <string name="notification_channel_summary_priority" msgid="7952654515769021553">"გამოჩნდება მიმოწერების სექციის ზედა ნაწილში მოლივლივე ბუშტის სახით, აჩვენებს პროფილის სურათს ჩაკეტილ ეკრანზე"</string>
+ <string name="notification_channel_summary_priority" msgid="7952654515769021553">"გამოჩნდება მიმოწერების ზედა ნაწილში ბუშტის სახით, აჩვენებს პროფილის სურათს ჩაკეტილ ეკრანზე"</string>
<string name="notification_conversation_channel_settings" msgid="2409977688430606835">"პარამეტრები"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"პრიორიტეტი"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g>-ს არ აქვს მიმოწერის ფუნქციების მხარდაჭერა"</string>
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"დაბალი პრიორიტეტის მქონე შეტყობინებების ხატულების ჩვენება"</string>
<string name="other" msgid="429768510980739978">"სხვა"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"პოზიცია <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. რედაქტირებისთვის, შეეხეთ ორმაგად."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. დასამატებლად, შეეხეთ ორმაგად."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"<xliff:g id="TILE_NAME">%1$s</xliff:g>-ის გადატანა"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"<xliff:g id="TILE_NAME">%1$s</xliff:g>-ის წაშლა"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"<xliff:g id="TILE_NAME">%1$s</xliff:g>-ის დამატება პოზიციაზე <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"<xliff:g id="TILE_NAME">%1$s</xliff:g>-ის გადატანა პოზიციაზე <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"ხანგრძლივად დააჭირეთ ჩართვის ღილაკს მართვის ახალი საშუალებების სანახავად"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"მართვის საშუალებების დამატება"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"მართვის საშუალებათა რედაქტირება"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"მედია-გამოსავლების დამატება"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"ჯგუფი"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"არჩეულია 1 მოწყობილობა"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"არჩეულია <xliff:g id="COUNT">%1$d</xliff:g> მოწყობილობა"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index ed6c05d60bb6..a4bf498edfff 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -28,7 +28,7 @@
<string name="battery_low_percent_format" msgid="4276661262843170964">"<xliff:g id="PERCENTAGE">%s</xliff:g> қалды"</string>
<string name="battery_low_percent_format_hybrid" msgid="3985614339605686167">"Пайдалану барысына байланысты <xliff:g id="PERCENTAGE">%1$s</xliff:g> заряд, шамамен <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> заряд, шамамен <xliff:g id="TIME">%2$s</xliff:g> қалды"</string>
- <string name="battery_low_percent_format_saver_started" msgid="4968468824040940688">"<xliff:g id="PERCENTAGE">%s</xliff:g> қалды. Battery Saver қосулы."</string>
+ <string name="battery_low_percent_format_saver_started" msgid="4968468824040940688">"<xliff:g id="PERCENTAGE">%s</xliff:g> қалды. Батареяны үнемдеу режимі қосулы."</string>
<string name="invalid_charger" msgid="4370074072117767416">"USB арқылы зарядтау мүмкін емес. Құрылғымен бірге берілген зарядтау құралын пайдаланыңыз."</string>
<string name="invalid_charger_title" msgid="938685362320735167">"USB арқылы зарядтау мүмкін емес"</string>
<string name="invalid_charger_text" msgid="2339310107232691577">"Құрылғымен бірге берілген зарядтау құралын пайдаланыңыз"</string>
@@ -419,7 +419,7 @@
<string name="quick_settings_night_secondary_label_on_at" msgid="3584738542293528235">"Қосылу уақыты: <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_secondary_label_until" msgid="1883981263191927372">"<xliff:g id="TIME">%s</xliff:g> дейін"</string>
<string name="quick_settings_ui_mode_night_label" msgid="1398928270610780470">"Қараңғы тақырып"</string>
- <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Battery Saver"</string>
+ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Батареяны үнемдеу режимі"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Күн батқанда қосу"</string>
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Күн шыққанға дейін"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Қосылу уақыты: <xliff:g id="TIME">%s</xliff:g>"</string>
@@ -711,14 +711,14 @@
<string name="notification_bubble_title" msgid="8330481035191903164">"Көпіршік"</string>
<string name="notification_automatic_title" msgid="3745465364578762652">"Автоматты"</string>
<string name="notification_channel_summary_low" msgid="4860617986908931158">"Дыбыс не діріл қолданылмайды"</string>
- <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Дыбыс не діріл қолданылмайды, төменде әңгімелер бөлімінде шығады"</string>
+ <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Дыбыс не діріл қолданылмайды, әңгімелер бөлімінің төмен жағында шығады"</string>
<string name="notification_channel_summary_default" msgid="3282930979307248890">"Телефон параметрлеріне байланысты шылдырлауы не дірілдеуі мүмкін"</string>
<string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Телефон параметрлеріне байланысты шылдырлауы не дірілдеуі мүмкін. <xliff:g id="APP_NAME">%1$s</xliff:g> чаттары әдепкісінше қалқымалы етіп көрсетіледі."</string>
<string name="notification_channel_summary_bubble" msgid="7235935211580860537">"Осы мазмұнға бекітілген қалқымалы таңбашамен назарыңызды өзіне тартады."</string>
<string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Хабарландыру дыбысының немесе дірілдің қосылуын жүйе анықтайтын болады"</string>
<string name="notification_channel_summary_priority" msgid="7952654515769021553">"Әңгімелер бөлімінің жоғарғы жағында тұрады, қалқыма хабар түрінде шығады, құлыптаулы экранда профиль суретін көрсетеді"</string>
<string name="notification_conversation_channel_settings" msgid="2409977688430606835">"Параметрлер"</string>
- <string name="notification_priority_title" msgid="2079708866333537093">"Маңыздылығы"</string>
+ <string name="notification_priority_title" msgid="2079708866333537093">"Маңызды"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> әңгімелесу функцияларын қолдамайды."</string>
<string name="bubble_overflow_empty_title" msgid="3120029421991510842">"Жақындағы қалқыма хабарлар жоқ"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2030874469510497397">"Соңғы және жабылған қалқыма хабарлар осы жерде көрсетіледі."</string>
@@ -774,7 +774,7 @@
</plurals>
<string name="battery_panel_title" msgid="5931157246673665963">"Батареяны пайдалану"</string>
<string name="battery_detail_charging_summary" msgid="8821202155297559706">"Зарядтау кезінде Батарея үнемдегіш қол жетімді емес"</string>
- <string name="battery_detail_switch_title" msgid="6940976502957380405">"Battery Saver"</string>
+ <string name="battery_detail_switch_title" msgid="6940976502957380405">"Батареяны үнемдеу режимі"</string>
<string name="battery_detail_switch_summary" msgid="3668748557848025990">"Өнімділікті және фондық деректерді азайтады"</string>
<string name="keyboard_key_button_template" msgid="8005673627272051429">"<xliff:g id="NAME">%1$s</xliff:g> түймесі"</string>
<string name="keyboard_key_home" msgid="3734400625170020657">"Home"</string>
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Маңызды емес хабарландыру белгішелерін көрсету"</string>
<string name="other" msgid="429768510980739978">"Басқа"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"<xliff:g id="POSITION">%1$d</xliff:g> орны, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Өңдеу үшін екі рет түртіңіз."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Қосу үшін екі рет түртіңіз."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"<xliff:g id="TILE_NAME">%1$s</xliff:g> жылжыту"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"<xliff:g id="TILE_NAME">%1$s</xliff:g> жою"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"<xliff:g id="TILE_NAME">%1$s</xliff:g> бөлшегін <xliff:g id="POSITION">%2$d</xliff:g>-позицияға енгізу"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"<xliff:g id="TILE_NAME">%1$s</xliff:g> бөлшегін <xliff:g id="POSITION">%2$d</xliff:g>-позицияға жылжыту"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Жаңа басқару элементтерін көру үшін \"Қуат\" түймесін басып тұрыңыз."</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Басқару элементтерін енгізу"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Басқару элементтерін өзгерту"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Шығыс сигналдарды қосу"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Топ"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 құрылғы таңдалды."</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> құрылғы таңдалды."</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 7636fdd6d8c2..7eaa3039947a 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -419,7 +419,7 @@
<string name="quick_settings_night_secondary_label_on_at" msgid="3584738542293528235">"បើក​នៅម៉ោង <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_secondary_label_until" msgid="1883981263191927372">"រហូតដល់​ម៉ោង <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_ui_mode_night_label" msgid="1398928270610780470">"រចនាប័ទ្ម​ងងឹត"</string>
- <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"កម្មវិធីសន្សំថ្ម"</string>
+ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"មុខងារ​សន្សំ​ថ្ម"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"បើក​នៅពេល​ថ្ងៃលិច"</string>
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"រហូត​ដល់​ពេល​ថ្ងៃរះ"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"បើកនៅម៉ោង <xliff:g id="TIME">%s</xliff:g>"</string>
@@ -774,7 +774,7 @@
</plurals>
<string name="battery_panel_title" msgid="5931157246673665963">"ការប្រើប្រាស់ថ្ម"</string>
<string name="battery_detail_charging_summary" msgid="8821202155297559706">"កម្មវិធីសន្សំថ្មមិនអាចប្រើបានអំឡុងពេលសាកថ្មទេ"</string>
- <string name="battery_detail_switch_title" msgid="6940976502957380405">"កម្មវិធីសន្សំថ្ម"</string>
+ <string name="battery_detail_switch_title" msgid="6940976502957380405">"មុខងារ​សន្សំ​ថ្ម"</string>
<string name="battery_detail_switch_summary" msgid="3668748557848025990">"កាត់បន្ថយប្រតិបត្តិការ និងទិន្នន័យផ្ទៃខាងក្រោយ"</string>
<string name="keyboard_key_button_template" msgid="8005673627272051429">"ប៊ូតុង <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="keyboard_key_home" msgid="3734400625170020657">"Home"</string>
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"បង្ហាញ​រូប​ការជូនដំណឹង​ដែលមានអាទិភាពទាប"</string>
<string name="other" msgid="429768510980739978">"ផ្សេងៗ"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"ទីតាំង <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>, ប៉ះពីរដងដើម្បីកែ"</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>, ប៉ះពីរដងដើម្បីបន្ថែម"</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"ផ្លាស់ទី <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"យក <xliff:g id="TILE_NAME">%1$s</xliff:g> ចេញ"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"បញ្ចូល <xliff:g id="TILE_NAME">%1$s</xliff:g> ទៅទីតាំង <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"ផ្លាស់ទី <xliff:g id="TILE_NAME">%1$s</xliff:g> ទៅទីតាំង <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"សង្កត់​ប៊ូតុង​ថាមពល ដើម្បី​មើលឃើញ​ការគ្រប់គ្រង​ថ្មីៗ"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"បញ្ចូល​ផ្ទាំងគ្រប់គ្រង"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"កែ​ផ្ទាំងគ្រប់គ្រង"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"បញ្ចូល​ឧបករណ៍​មេឌៀ"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"ក្រុម"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"បានជ្រើសរើស​ឧបករណ៍ 1"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"បានជ្រើសរើស​ឧបករណ៍ <xliff:g id="COUNT">%1$d</xliff:g>"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 7980790c3feb..79f845290365 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"ಕಡಿಮೆ-ಆದ್ಯತೆ ಸೂಚನೆಯ ಐಕಾನ್‌ಗಳನ್ನು ತೋರಿಸಿ"</string>
<string name="other" msgid="429768510980739978">"ಇತರ"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"ಸ್ಥಳ <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. ಎಡಿಟ್ ಮಾಡಲು ಡಬಲ್ ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. ಸೇರಿಸಲು ಡಬಲ್ ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"<xliff:g id="TILE_NAME">%1$s</xliff:g> ಸರಿಸಿ"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"<xliff:g id="TILE_NAME">%1$s</xliff:g> ತೆಗೆದುಹಾಕಿ"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"<xliff:g id="TILE_NAME">%1$s</xliff:g> ಅನ್ನು <xliff:g id="POSITION">%2$d</xliff:g> ಗೆ ಸೇರಿಸಿ"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"<xliff:g id="TILE_NAME">%1$s</xliff:g> ಅನ್ನು <xliff:g id="POSITION">%2$d</xliff:g> ಗೆ ಸರಿಸಿ"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"ಹೊಸ ನಿಯಂತ್ರಣಗಳನ್ನು ನೋಡಲು ಪವರ್ ಬಟನ್ ಹಿಡಿದುಕೊಳ್ಳಿ"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"ನಿಯಂತ್ರಣಗಳನ್ನು ಸೇರಿಸಿ"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"ನಿಯಂತ್ರಣಗಳನ್ನು ಎಡಿಟ್ ಮಾಡಿ"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"ಔಟ್‌ಪುಟ್‌ಗಳನ್ನು ಸೇರಿಸಿ"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"ಗುಂಪು"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 ಸಾಧನವನ್ನು ಆಯ್ಕೆ ಮಾಡಲಾಗಿದೆ"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> ಸಾಧನಗಳನ್ನು ಆಯ್ಕೆ ಮಾಡಲಾಗಿದೆ"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index ce59c6f796c5..6a755e52f058 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -716,7 +716,7 @@
<string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"휴대전화 설정에 따라 벨소리나 진동이 울릴 수 있습니다. 기본적으로 <xliff:g id="APP_NAME">%1$s</xliff:g>의 대화는 대화창으로 표시됩니다."</string>
<string name="notification_channel_summary_bubble" msgid="7235935211580860537">"이 콘텐츠로 연결되는 플로팅 바로가기로 사용자의 주의를 끕니다."</string>
<string name="notification_channel_summary_automatic" msgid="5813109268050235275">"시스템에서 알림 시 소리 또는 진동을 사용할지 결정하도록 허용합니다."</string>
- <string name="notification_channel_summary_priority" msgid="7952654515769021553">"대화 섹션 상단에 표시, 플로팅 대화창으로 표시, 그리고 잠금 화면에 프로필 사진이 표시됩니다."</string>
+ <string name="notification_channel_summary_priority" msgid="7952654515769021553">"대화 섹션 상단에 표시, 플로팅 대화창으로 표시, 그리고 잠금 화면에 프로필 사진이 표시됨"</string>
<string name="notification_conversation_channel_settings" msgid="2409977688430606835">"설정"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"우선순위"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> 앱은 대화 기능을 지원하지 않습니다."</string>
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"우선순위가 낮은 알림 아이콘 표시"</string>
<string name="other" msgid="429768510980739978">"기타"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"위치 <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. 수정하려면 두 번 탭하세요."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. 추가하려면 두 번 탭하세요."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"<xliff:g id="TILE_NAME">%1$s</xliff:g> 이동"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"<xliff:g id="TILE_NAME">%1$s</xliff:g> 삭제"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"<xliff:g id="TILE_NAME">%1$s</xliff:g> 타일을 위치 <xliff:g id="POSITION">%2$d</xliff:g>에 추가"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"<xliff:g id="TILE_NAME">%1$s</xliff:g> 타일을 위치 <xliff:g id="POSITION">%2$d</xliff:g>(으)로 이동"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"새 컨트롤을 보려면 전원 버튼을 길게 누르세요."</string>
<string name="controls_menu_add" msgid="4447246119229920050">"컨트롤 추가"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"컨트롤 수정"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"출력 추가"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"그룹"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"기기 1대 선택됨"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"기기 <xliff:g id="COUNT">%1$d</xliff:g>대 선택됨"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index 57132e43dae9..a740d07f7eac 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -716,7 +716,7 @@
<string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Телефондун жөндөөлөрүнө жараша шыңгырап же дирилдеши мүмкүн. <xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосундагы жазышуулар демейки жөндөө боюнча калкып чыкма билдирмелер түрүндө көрүнөт."</string>
<string name="notification_channel_summary_bubble" msgid="7235935211580860537">"Калкыма ыкчам баскыч менен көңүлүңүздү бул мазмунга буруп турат."</string>
<string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Билдирменин үнүн чыгартууну же басууну тутумга тапшырыңыз"</string>
- <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Жазышуу бөлүмүнүн жогорку жагында калкып чыкма билдирме түрүндө көрүнүп, профиль сүрөтү кулпуланган экрандан чагылдырылат"</string>
+ <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Жазышуулар тизмесинин өйдө жагында калкып чыкма билдирме түрүндө көрүнүп, профиль сүрөтү кулпуланган экрандан чагылдырылат"</string>
<string name="notification_conversation_channel_settings" msgid="2409977688430606835">"Жөндөөлөр"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Маанилүүлүгү"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> жазышуу функцияларын колдоого албайт"</string>
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Анча маанилүү эмес билдирменин сүрөтчөлөрүн көрсөтүү"</string>
<string name="other" msgid="429768510980739978">"Башка"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Орду - <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Түзөтүү үчүн эки жолу таптаңыз."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Кошуу үчүн эки жолу таптаңыз."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"<xliff:g id="TILE_NAME">%1$s</xliff:g> дегенди жылдыруу"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"<xliff:g id="TILE_NAME">%1$s</xliff:g> дегенди алып салуу"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"<xliff:g id="TILE_NAME">%1$s</xliff:g> плиткасы <xliff:g id="POSITION">%2$d</xliff:g>-позицияга кошулсун"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"<xliff:g id="TILE_NAME">%1$s</xliff:g> плиткасы <xliff:g id="POSITION">%2$d</xliff:g>-позицияга кошулсун"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Башкаруу элементтерин көрүү үчүн күйгүзүү/өчүрүү баскычын коё бербей басып туруңуз"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Башкаруу элементтерин кошуу"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Башкаруу элементтерин түзөтүү"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Медиа түзмөктөрдү кошуу"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Топ"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 түзмөк тандалды"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> түзмөк тандалды"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index 0ff4509891b0..1fb2ca0b0bd2 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"ສະແດງໄອຄອນການແຈ້ງເຕືອນຄວາມສຳຄັນຕ່ຳ"</string>
<string name="other" msgid="429768510980739978">"ອື່ນໆ"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"ຕຳແໜ່ງ <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. ແຕະສອງເທື່ອເພື່ອແກ້ໄຂ."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. ແຕະສອງເທື່ອເພື່ອເພີ່ມ."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"ຍ້າຍ <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"ລຶບ <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"ເພີ່ມ <xliff:g id="TILE_NAME">%1$s</xliff:g> ໄປຕຳແໜ່ງ <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"ຍ້າຍ <xliff:g id="TILE_NAME">%1$s</xliff:g> ໄປຕຳແໜ່ງ <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"ກົດປຸ່ມເປີດປິດຄ້າງໄວ້ເພື່ອເບິ່ງການຄວບຄຸມໃໝ່"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"ເພີ່ມການຄວບຄຸມ"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"ແກ້ໄຂການຄວບຄຸມ"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"ເພີ່ມເອົ້າພຸດ"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"ກຸ່ມ"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"ເລືອກ 1 ອຸປະກອນແລ້ວ"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"ເລືອກ <xliff:g id="COUNT">%1$d</xliff:g> ອຸປະກອນແລ້ວ"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 239151ac5be4..bd24722436dd 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -784,7 +784,7 @@
</plurals>
<string name="battery_panel_title" msgid="5931157246673665963">"Akum. energ. vartoj."</string>
<string name="battery_detail_charging_summary" msgid="8821202155297559706">"Akumuliatoriaus tausojimo priemonė nepasiekiama įkraunant"</string>
- <string name="battery_detail_switch_title" msgid="6940976502957380405">"Akumuliat. taus. pr."</string>
+ <string name="battery_detail_switch_title" msgid="6940976502957380405">"Akum. taus. pr."</string>
<string name="battery_detail_switch_summary" msgid="3668748557848025990">"Sumažinamas našumas ir foninių duomenų naudojimas"</string>
<string name="keyboard_key_button_template" msgid="8005673627272051429">"Mygtukas <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="keyboard_key_home" msgid="3734400625170020657">"Pagrindinis"</string>
@@ -889,12 +889,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Rodyti mažo prioriteto pranešimų piktogramas"</string>
<string name="other" msgid="429768510980739978">"Kita"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"<xliff:g id="POSITION">%1$d</xliff:g> padėtis, išklotinės elementas „<xliff:g id="TILE_NAME">%2$s</xliff:g>“. Dukart palieskite, kad redaguotumėte."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"Išklotinės elementas „<xliff:g id="TILE_NAME">%1$s</xliff:g>“. Dukart palieskite, kad pridėtumėte."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Perkelti išklotinės elementą „<xliff:g id="TILE_NAME">%1$s</xliff:g>“"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Pašalinti išklotinės elementą „<xliff:g id="TILE_NAME">%1$s</xliff:g>“"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Pridėti išklotinės elementą „<xliff:g id="TILE_NAME">%1$s</xliff:g>“ į <xliff:g id="POSITION">%2$d</xliff:g> padėtį"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Perkelti išklotinės elementą „<xliff:g id="TILE_NAME">%1$s</xliff:g>“ į <xliff:g id="POSITION">%2$d</xliff:g> padėtį"</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_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>
@@ -1079,4 +1087,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Jei norite peržiūrėti naujus valdiklius, laikykite paspaudę maitinimo mygtuką"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Pridėti valdiklių"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Redaguoti valdiklius"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Išvesčių pridėjimas"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupė"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Pasirinktas 1 įrenginys"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Pasirinkta įrenginių: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 1552a9e68363..e8d9a4d8c797 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -714,7 +714,7 @@
<string name="notification_bubble_title" msgid="8330481035191903164">"Burbulis"</string>
<string name="notification_automatic_title" msgid="3745465364578762652">"Automātiski"</string>
<string name="notification_channel_summary_low" msgid="4860617986908931158">"Nav skaņas signāla vai vibrācijas"</string>
- <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Nav skaņas signāla vai vibrācijas, kā arī atrodas zemāk sarunu sadaļā"</string>
+ <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Nav skaņas signāla vai vibrācijas, kā arī atrodas tālāk sarunu sadaļā"</string>
<string name="notification_channel_summary_default" msgid="3282930979307248890">"Atkarībā no tālruņa iestatījumiem var zvanīt vai vibrēt"</string>
<string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Atkarībā no tālruņa iestatījumiem var zvanīt vai vibrēt. Sarunas no lietotnes <xliff:g id="APP_NAME">%1$s</xliff:g> pēc noklusējuma tiek parādītas burbulī."</string>
<string name="notification_channel_summary_bubble" msgid="7235935211580860537">"Piesaista jūsu uzmanību, rādot peldošu saīsni uz šo saturu."</string>
@@ -884,12 +884,20 @@
</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>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"<xliff:g id="POSITION">%1$d</xliff:g>. pozīcija, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Lai rediģētu, veiciet dubultskārienu."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Lai pievienotu, veiciet dubultskārienu."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Pārvietot elementu <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Noņemt elementu <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Pievienot elementu “<xliff:g id="TILE_NAME">%1$s</xliff:g>” <xliff:g id="POSITION">%2$d</xliff:g>. pozīcijā"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Pārvietot elementu “<xliff:g id="TILE_NAME">%1$s</xliff:g>” uz <xliff:g id="POSITION">%2$d</xliff:g>. pozīciju"</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_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>
@@ -1073,4 +1081,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Nospiediet barošanas pogu un turiet to, lai skatītu jaunas vadīklas"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Pievienot vadīklas"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Rediģēt vadīklas"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Izejas ierīču pievienošana"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupa"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Atlasīta viena ierīce"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Atlasītas vairākas ierīces (kopā <xliff:g id="COUNT">%1$d</xliff:g>)"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 42b758fd518b..164cb96bf046 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -711,14 +711,14 @@
<string name="notification_bubble_title" msgid="8330481035191903164">"Балонче"</string>
<string name="notification_automatic_title" msgid="3745465364578762652">"Автоматски"</string>
<string name="notification_channel_summary_low" msgid="4860617986908931158">"Без звук или вибрации"</string>
- <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Без звук или вибрации и се појавува под делот за разговор"</string>
+ <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Без звук или вибрации и се појавува подолу во делот со разговори"</string>
<string name="notification_channel_summary_default" msgid="3282930979307248890">"Може да ѕвони или вибрира во зависност од поставките на телефонот"</string>
<string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Може да ѕвони или вибрира во зависност од поставките на телефонот Стандардно, разговорите од <xliff:g id="APP_NAME">%1$s</xliff:g> се во балончиња."</string>
<string name="notification_channel_summary_bubble" msgid="7235935211580860537">"Ви го задржува вниманието со лебдечка кратенка на содржинава."</string>
<string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Дозволете системот да определи дали известувањево треба да испушти звук или да вибрира"</string>
<string name="notification_channel_summary_priority" msgid="7952654515769021553">"Се појавува на горниот дел од секцијата на разговорот во вид на лебдечко меурче, покажувајќи ја профилната слика на заклучениот екран"</string>
<string name="notification_conversation_channel_settings" msgid="2409977688430606835">"Поставки"</string>
- <string name="notification_priority_title" msgid="2079708866333537093">"Приоритет"</string>
+ <string name="notification_priority_title" msgid="2079708866333537093">"Приоритетно"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> не поддржува функции за разговор"</string>
<string name="bubble_overflow_empty_title" msgid="3120029421991510842">"Нема неодамнешни балончиња"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2030874469510497397">"Неодамнешните и отфрлените балончиња ќе се појавуваат тука"</string>
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Прикажувај икони за известувања со низок приоритет"</string>
<string name="other" msgid="429768510980739978">"Друго"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Место <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Допрете двапати за уредување."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Допрете двапати за додавање."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Преместете <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Отстранете <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Додајте <xliff:g id="TILE_NAME">%1$s</xliff:g> на позицијата <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Преместете <xliff:g id="TILE_NAME">%1$s</xliff:g> на позицијата <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Задржете го копчето за вклучување за да ги видите новите контроли"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Додајте контроли"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Изменете ги контролите"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Додајте излези"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Група"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Избран е 1 уред"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Избрани се <xliff:g id="COUNT">%1$d</xliff:g> уреди"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 2e68d3b8717d..ba379d3b7116 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"പ്രാധാന്യം കുറഞ്ഞ അറിയിപ്പ് ചിഹ്‌നങ്ങൾ"</string>
<string name="other" msgid="429768510980739978">"മറ്റുള്ളവ"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"സ്ഥാനം <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. എഡിറ്റുചെയ്യുന്നതിന് രണ്ടുതവണ ടാപ്പുചെയ്യുക."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. ചേർക്കാൻ രണ്ടുതവണ ടാപ്പുചെയ്യുക."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"<xliff:g id="TILE_NAME">%1$s</xliff:g> നീക്കുക"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"<xliff:g id="TILE_NAME">%1$s</xliff:g> നീക്കംചെയ്യുക"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"<xliff:g id="POSITION">%2$d</xliff:g> സ്ഥാനത്തേയ്ക്ക് <xliff:g id="TILE_NAME">%1$s</xliff:g> ചേർക്കുക"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"<xliff:g id="POSITION">%2$d</xliff:g> സ്ഥാനത്തേയ്ക്ക് <xliff:g id="TILE_NAME">%1$s</xliff:g> നീക്കുക"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"പുതിയ നിയന്ത്രണങ്ങൾ കാണാൻ പവർ ബട്ടൺ പിടിക്കുക"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"നിയന്ത്രണങ്ങൾ ചേർക്കുക"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"നിയന്ത്രണങ്ങൾ എഡിറ്റ് ചെയ്യുക"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"ഔട്ട്പുട്ടുകൾ ചേർക്കുക"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"ഗ്രൂപ്പ്"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"ഒരു ഉപകരണം തിരഞ്ഞെടുത്തു"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> ഉപകരണങ്ങൾ തിരഞ്ഞെടുത്തു"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index fab7a5531ca1..56c5ef27baec 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -711,7 +711,7 @@
<string name="notification_bubble_title" msgid="8330481035191903164">"Бөмбөлөг"</string>
<string name="notification_automatic_title" msgid="3745465364578762652">"Автомат"</string>
<string name="notification_channel_summary_low" msgid="4860617986908931158">"Дуу эсвэл чичиргээ байхгүй"</string>
- <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Дуу эсвэл чичиргээ байхгүй бөгөөд харицан ярианы хэсгийн доод талд харагдана"</string>
+ <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Дуу эсвэл чичиргээ байхгүй бөгөөд харилцан ярианы хэсгийн доод талд харагдана"</string>
<string name="notification_channel_summary_default" msgid="3282930979307248890">"Утасны тохиргоонд тулгуурлан хонх дуугаргах эсвэл чичирхийлж болзошгүй"</string>
<string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Утасны тохиргоонд тулгуурлан хонх дуугаргах эсвэл чичирхийлж болзошгүй. <xliff:g id="APP_NAME">%1$s</xliff:g>-н харилцан яриаг өгөгдмөл тохиргооны дагуу бөмбөлөг болгоно."</string>
<string name="notification_channel_summary_bubble" msgid="7235935211580860537">"Энэ контентын хөвөн гарч ирэх товчлолтойгоор таны анхаарлыг татдаг."</string>
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Бага ач холбогдолтой мэдэгдлийн дүрс тэмдгийг харуулах"</string>
<string name="other" msgid="429768510980739978">"Бусад"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Байршил <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Засахын тулд 2 удаа дарна уу."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Нэмэхийн тулд 2 удаа дарна уу."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"<xliff:g id="TILE_NAME">%1$s</xliff:g>-г зөөх"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"<xliff:g id="TILE_NAME">%1$s</xliff:g>-г устгах"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"<xliff:g id="TILE_NAME">%1$s</xliff:g>-г <xliff:g id="POSITION">%2$d</xliff:g> байрлалд нэмэх"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"<xliff:g id="TILE_NAME">%1$s</xliff:g>-г байрлал <xliff:g id="POSITION">%2$d</xliff:g> руу зөөх"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Шинэ хяналтыг харахын тулд асаах товчийг удаан дарна уу"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Хяналт нэмэх"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Хяналтыг өөрчлөх"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Гаралт нэмэх"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Бүлэг"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 төхөөрөмж сонгосон"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> төхөөрөмж сонгосон"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 31a0a7a8da31..728065fbd5cc 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"कमी प्राधान्य सूचना आयकन दर्शवा"</string>
<string name="other" msgid="429768510980739978">"अन्य"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"स्थिती <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. संपादित करण्यासाठी दोनदा टॅप करा."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g> . जोडण्यासाठी दोनदा टॅप करा."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"<xliff:g id="TILE_NAME">%1$s</xliff:g> हलवा"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"<xliff:g id="TILE_NAME">%1$s</xliff:g> काढा"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"<xliff:g id="TILE_NAME">%1$s</xliff:g> <xliff:g id="POSITION">%2$d</xliff:g> स्थानावर जोडा"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"<xliff:g id="TILE_NAME">%1$s</xliff:g> <xliff:g id="POSITION">%2$d</xliff:g> स्थानावर हलवा"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"नवीन नियंत्रणे पाहण्यासाठी पॉवर बटण धरून ठेवा"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"नियंत्रणे जोडा"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"नियंत्रणे व्यवस्थापित करा"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"आउटपुट जोडा"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"गट"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"एक डिव्हाइस निवडले"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> डिव्हाइस निवडली आहेत"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index b147b6e7a023..62d845c3f72a 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Tunjukkan ikon pemberitahuan keutamaan rendah"</string>
<string name="other" msgid="429768510980739978">"Lain-lain"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Kedudukan <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Dwiketik untuk mengedit."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Dwiketik untuk menambah."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Alihkan <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Alih keluar <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Tambahkan <xliff:g id="TILE_NAME">%1$s</xliff:g> pada kedudukan <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Alihkan <xliff:g id="TILE_NAME">%1$s</xliff:g> ke kedudukan <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Tahan butang Kuasa untuk melihat kawalan baharu"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Tambah kawalan"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Edit kawalan"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Tambah output"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Kumpulan"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 peranti dipilih"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> peranti dipilih"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 636e83643ee6..e72f16925eab 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"အရေးမကြီးသော အကြောင်းကြားချက် သင်္ကေတများ ပြရန်"</string>
<string name="other" msgid="429768510980739978">"အခြား"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"<xliff:g id="POSITION">%1$d</xliff:g>၊ <xliff:g id="TILE_NAME">%2$s</xliff:g> နေရာ။ တည်းဖြတ်ရန် နှစ်ချက်တို့ပါ။"</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>။ ပေါင်းထည့်ရန် နှစ်ချက်တို့ပါ။"</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"<xliff:g id="TILE_NAME">%1$s</xliff:g> ကိုရွှေ့ပါ"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"<xliff:g id="TILE_NAME">%1$s</xliff:g> ကိုဖယ်ရှားပါ"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"<xliff:g id="TILE_NAME">%1$s</xliff:g> ကို အနေအထား <xliff:g id="POSITION">%2$d</xliff:g> သို့ ပေါင်းထည့်ရန်"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"<xliff:g id="TILE_NAME">%1$s</xliff:g> ကို အနေအထား <xliff:g id="POSITION">%2$d</xliff:g> သို့ ရွှေ့ရန်"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"ထိန်းချုပ်မှုအသစ်များ ကြည့်ရန် ဖွင့်ပိတ်ခလုတ်ကို ဖိထားပါ"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"ထိန်းချုပ်မှုများ ထည့်ရန်"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"ထိန်းချုပ်မှုများ တည်းဖြတ်ရန်"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"မီဒီယာအထွက်များ ထည့်ရန်"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"အုပ်စု"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"စက်ပစ္စည်း ၁ ခုကို ရွေးချယ်ထားသည်"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"စက်ပစ္စည်း <xliff:g id="COUNT">%1$d</xliff:g> ခုကို ရွေးချယ်ထားသည်"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 93ef3af9b037..41aabcb89b65 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Vis ikoner for varsler med lav prioritet"</string>
<string name="other" msgid="429768510980739978">"Annet"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Plassering <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Dobbelttrykk for å endre."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Dobbelttrykk for å legge til."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Flytt <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Fjern <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Legg til <xliff:g id="TILE_NAME">%1$s</xliff:g> i posisjon <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Flytt <xliff:g id="TILE_NAME">%1$s</xliff:g> til posisjon <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Hold inne av/på-knappen for å se kontroller"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Legg til kontroller"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Endre kontroller"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Legg til utenheter"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Gruppe"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 enhet er valgt"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> enheter er valgt"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index d218e3939304..0cf09d2bdd0d 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -43,7 +43,7 @@
<string name="status_bar_settings_mute_label" msgid="914392730086057522">"म्युट गर्नुहोस्"</string>
<string name="status_bar_settings_auto_brightness_label" msgid="2151934479226017725">"स्वतः"</string>
<string name="status_bar_settings_notifications" msgid="5285316949980621438">"सूचनाहरू"</string>
- <string name="bluetooth_tethered" msgid="4171071193052799041">"ब्लुटुथ टेथर भयो"</string>
+ <string name="bluetooth_tethered" msgid="4171071193052799041">"ब्लुटुथ टेदर भयो"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="2972273031043777851">"इनपुट विधिहरू सेटअप गर्नुहोस्"</string>
<string name="status_bar_use_physical_keyboard" msgid="4849251850931213371">"वास्तविक किबोर्ड"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> लाई <xliff:g id="USB_DEVICE">%2$s</xliff:g> माथि पहुँच राख्ने अनुमति दिने हो?"</string>
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"कम प्राथमिकताका सूचना आइकनहरू देखाउनुहोस्"</string>
<string name="other" msgid="429768510980739978">"अन्य"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"स्थिति <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>। सम्पादन गर्नाका लागि डबल ट्याप गर्नुहोस्।"</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>। थप्नका लागि डबल ट्याप गर्नुहोस्।"</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"<xliff:g id="TILE_NAME">%1$s</xliff:g> लाई सार्नुहोस्"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"<xliff:g id="TILE_NAME">%1$s</xliff:g> लाई हटाउनुहोस्"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"<xliff:g id="TILE_NAME">%1$s</xliff:g> लाई <xliff:g id="POSITION">%2$d</xliff:g> स्थितिमा थप्नुहोस्"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"<xliff:g id="TILE_NAME">%1$s</xliff:g> लाई <xliff:g id="POSITION">%2$d</xliff:g> स्थितिमा सार्नुहोस्"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"नयाँ नियन्त्रण सुविधाहरू हेर्न पावर बटन थिचिराख्नुहोस्"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"नियन्त्रण सुविधाहरू थप्नुहोस्"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"नियन्त्रण सुविधाहरू सम्पादन गर्नु…"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"आउटपुट यन्त्रहरू थप्नुहोस्"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"समूह"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"१ यन्त्र चयन गरियो"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> वटा यन्त्र चयन गरिए"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 3d61ee4adaf7..49e097e5a047 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -711,12 +711,12 @@
<string name="notification_bubble_title" msgid="8330481035191903164">"Bubbel"</string>
<string name="notification_automatic_title" msgid="3745465364578762652">"Automatisch"</string>
<string name="notification_channel_summary_low" msgid="4860617986908931158">"Geen geluid of trilling"</string>
- <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Geen geluid of trilling en wordt op een lagere positie in het gedeelte met gesprekken weergegeven"</string>
+ <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Geen geluid of trilling en wordt lager in het gedeelte met gesprekken weergegeven"</string>
<string name="notification_channel_summary_default" msgid="3282930979307248890">"Kan overgaan of trillen op basis van de telefooninstellingen"</string>
<string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Kan overgaan of trillen op basis van de telefooninstellingen. Gesprekken uit <xliff:g id="APP_NAME">%1$s</xliff:g> worden standaard als bubbels weergegeven."</string>
<string name="notification_channel_summary_bubble" msgid="7235935211580860537">"Trekt de aandacht met een zwevende snelkoppeling naar deze content."</string>
<string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Het systeem laten bepalen of deze melding geluid moet maken of moet trillen"</string>
- <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Wordt bovenaan het gedeelte met gesprekken weergegeven, verschijnt als zwevende bubbel, geeft de profielfoto weer op het vergrendelscherm"</string>
+ <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Wordt bovenaan het gespreksgedeelte weergegeven, verschijnt als zwevende bubbel, geeft profielfoto weer op vergrendelscherm"</string>
<string name="notification_conversation_channel_settings" msgid="2409977688430606835">"Instellingen"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prioriteit"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> ondersteunt geen gespreksfuncties"</string>
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Pictogrammen voor meldingen met lage prioriteit weergeven"</string>
<string name="other" msgid="429768510980739978">"Overig"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Positie <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Dubbeltik om te bewerken."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Dubbeltik om toe te voegen."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"<xliff:g id="TILE_NAME">%1$s</xliff:g> verplaatsen"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"<xliff:g id="TILE_NAME">%1$s</xliff:g> verwijderen"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"<xliff:g id="TILE_NAME">%1$s</xliff:g> toevoegen aan positie <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"<xliff:g id="TILE_NAME">%1$s</xliff:g> verplaatsen naar positie <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Houd de aan/uit-knop ingedrukt om nieuwe bedieningselementen te bekijken"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Bedieningselementen toevoegen"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Bedieningselementen bewerken"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Uitvoer toevoegen"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Groep"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Eén apparaat geselecteerd"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> apparaten geselecteerd"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index dc38e39e054c..c3105ff55bc7 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -716,7 +716,7 @@
<string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"ଫୋନ୍ ସେଟିଂସ୍ ଆଧାରରେ ରିଙ୍ଗ କିମ୍ବା ଭାଇବ୍ରେଟ୍ ହୋଇପାରେ। <xliff:g id="APP_NAME">%1$s</xliff:g>ରୁ ବାର୍ତ୍ତାଳାପଗୁଡ଼ିକ ଡିଫଲ୍ଟ ଭାବରେ ବବଲ୍ ହୁଏ।"</string>
<string name="notification_channel_summary_bubble" msgid="7235935211580860537">"ଏହି ବିଷୟବସ୍ତୁ ପାଇଁ ଏକ ଭାସମାନ ସର୍ଟକଟ୍ ସହ ଆପଣଙ୍କର ଧ୍ୟାନ ଦିଅନ୍ତୁ।"</string>
<string name="notification_channel_summary_automatic" msgid="5813109268050235275">"ଏହି ବିଜ୍ଞପ୍ତି ପ୍ରାପ୍ତ ହେବା ସମୟରେ ସାଉଣ୍ଡ ହେବା ଉଚିତ ନା ଭାଇବ୍ରେସନ୍ ତାହା ସିଷ୍ଟମକୁ ସ୍ଥିର କରିବାକୁ ଦିଅନ୍ତୁ"</string>
- <string name="notification_channel_summary_priority" msgid="7952654515769021553">"ବାର୍ତ୍ତାଳାପ ବିଭାଗର ଶୀର୍ଷରେ ଦେଖାଏ, ଭାସମାନ ବବଲ୍ ଭାବେ ଦେଖାଯାଏ, ଲକ୍ ସ୍କ୍ରିନରେ ପ୍ରୋଫାଇଲ୍ ଛବି ଡିସପ୍ଲେ କରେ"</string>
+ <string name="notification_channel_summary_priority" msgid="7952654515769021553">"ବାର୍ତ୍ତାଳାପ ବିଭାଗର ଶୀର୍ଷରେ ଦେଖାଏ, ଫ୍ଲୋଟିଂ ବବଲ୍ ଭାବେ ଦେଖାଯାଏ, ଲକ୍ ସ୍କ୍ରିନରେ ପ୍ରୋଫାଇଲ୍ ଛବି ଡିସପ୍ଲେ କରେ"</string>
<string name="notification_conversation_channel_settings" msgid="2409977688430606835">"ସେଟିଂସ୍"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"ପ୍ରାଥମିକତା"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> ବାର୍ତ୍ତାଳାପ ଫିଚରଗୁଡ଼ିକୁ ସମର୍ଥନ କରେ ନାହିଁ"</string>
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"କମ୍‍-ଅଗ୍ରାଧିକାର ବିଜ୍ଞପ୍ତି ଆଇକନ୍‍ ଦେଖାନ୍ତୁ"</string>
<string name="other" msgid="429768510980739978">"ଅନ୍ୟାନ୍ୟ"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"ଅବସ୍ଥାନ <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>। ଏଡିଟ୍ କରିବାକୁ ଡବଲ୍‍-ଟାପ୍‍ କରନ୍ତୁ।"</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>। ଯୋଡ଼ିବା ପାଇଁ ଡବଲ୍‍-ଟାପ୍‍ କରନ୍ତୁ।"</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"<xliff:g id="TILE_NAME">%1$s</xliff:g> ନିଅନ୍ତୁ"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"<xliff:g id="TILE_NAME">%1$s</xliff:g> ବାହାର କରିଦିଅନ୍ତୁ"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"<xliff:g id="POSITION">%2$d</xliff:g> ଅବସ୍ଥାନକୁ <xliff:g id="TILE_NAME">%1$s</xliff:g> ଯୋଡନ୍ତୁ"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"<xliff:g id="POSITION">%2$d</xliff:g> ଅବସ୍ଥାନକୁ <xliff:g id="TILE_NAME">%1$s</xliff:g> ଘୁଞ୍ଚାନ୍ତୁ"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"ନୂଆ ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକୁ ଦେଖିବା ପାଇଁ ପାୱାର ବଟନକୁ ଧରି ରଖନ୍ତୁ"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକ ଯୋଗ କରନ୍ତୁ"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକ ସମ୍ପାଦନ କରନ୍ତୁ"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"ଆଉଟପୁଟ୍ ଯୋଗ କରନ୍ତୁ"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"ଗୋଷ୍ଠୀ"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1ଟି ଡିଭାଇସ୍ ଚୟନ କରାଯାଇଛି"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g>ଟି ଡିଭାଇସ୍ ଚୟନ କରାଯାଇଛି"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 8d15d176ead4..b12dfb3954ee 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"ਘੱਟ ਤਰਜੀਹ ਵਾਲੇ ਸੂਚਨਾ ਪ੍ਰਤੀਕਾਂ ਨੂੰ ਦਿਖਾਓ"</string>
<string name="other" msgid="429768510980739978">"ਹੋਰ"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"ਸਥਿਤੀ <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>। ਸੰਪਾਦਨ ਲਈ ਦੋ ਵਾਰ ਟੈਪ ਕਰੋ।"</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>। ਸ਼ਾਮਲ ਕਰਨ ਲਈ ਦੋ ਵਾਰ ਟੈਪ ਕਰੋ।"</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"<xliff:g id="TILE_NAME">%1$s</xliff:g> ਨੂੰ ਤਬਦੀਲ ਕਰੋ"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"<xliff:g id="TILE_NAME">%1$s</xliff:g> ਹਟਾਓ"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"<xliff:g id="TILE_NAME">%1$s</xliff:g> ਨੂੰ <xliff:g id="POSITION">%2$d</xliff:g> ਸਥਾਨ \'ਤੇ ਸ਼ਾਮਲ ਕਰੋ"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"<xliff:g id="TILE_NAME">%1$s</xliff:g> ਨੂੰ <xliff:g id="POSITION">%2$d</xliff:g> ਸਥਾਨ \'ਤੇ ਲਿਜਾਓ"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"ਨਵੇਂ ਕੰਟਰੋਲ ਦੇਖਣ ਲਈ ਪਾਵਰ ਬਟਨ ਦਬਾਈ ਰੱਖੋ"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"ਕੰਟਰੋਲ ਸ਼ਾਮਲ ਕਰੋ"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"ਕੰਟਰੋਲਾਂ ਦਾ ਸੰਪਾਦਨ ਕਰੋ"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"ਆਊਟਪੁੱਟ ਸ਼ਾਮਲ ਕਰੋ"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"ਗਰੁੱਪ"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 ਡੀਵਾਈਸ ਨੂੰ ਚੁਣਿਆ ਗਿਆ"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> ਡੀਵਾਈਸਾਂ ਨੂੰ ਚੁਣਿਆ ਗਿਆ"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index b531be0ff724..d195977f460b 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -889,12 +889,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Pokazuj ikony powiadomień o niskim priorytecie"</string>
<string name="other" msgid="429768510980739978">"Inne"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Położenie <xliff:g id="POSITION">%1$d</xliff:g>, kafelek <xliff:g id="TILE_NAME">%2$s</xliff:g>. Kliknij dwukrotnie, by edytować."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"Kafelek <xliff:g id="TILE_NAME">%1$s</xliff:g>. Kliknij dwukrotnie, by dodać."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Przenieś kafelek <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Usuń kafelek <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Dodaj kafelek <xliff:g id="TILE_NAME">%1$s</xliff:g> w położeniu <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Przenieś kafelek <xliff:g id="TILE_NAME">%1$s</xliff:g> w położenie <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1079,4 +1087,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Przytrzymaj przycisk zasilania, by zobaczyć nowe elementy sterujące"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Dodaj elementy sterujące"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Edytuj elementy sterujące"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Dodaj urządzenia wyjściowe"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupa"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Wybrano 1 urządzenie"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Wybrane urządzenia: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 67f161151d47..6f96f37bc302 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -712,13 +712,13 @@
<string name="notification_automatic_title" msgid="3745465364578762652">"Automática"</string>
<string name="notification_channel_summary_low" msgid="4860617986908931158">"Som e vibração desativados"</string>
<string name="notification_conversation_summary_low" msgid="1734433426085468009">"O som e a vibração estão desativados, e o balão aparece na parte inferior da seção de conversa"</string>
- <string name="notification_channel_summary_default" msgid="3282930979307248890">"Pode vibrar ou tocar com base nas configurações do smartphone"</string>
+ <string name="notification_channel_summary_default" msgid="3282930979307248890">"Podem vibrar ou tocar com base nas configurações do smartphone"</string>
<string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Pode vibrar ou tocar com base nas configurações do smartphone. As conversas do app <xliff:g id="APP_NAME">%1$s</xliff:g> aparecem em balões por padrão."</string>
<string name="notification_channel_summary_bubble" msgid="7235935211580860537">"Mantém sua atenção com um atalho flutuante para esse conteúdo."</string>
<string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Faça com que o sistema determine se a notificação resultará em som ou vibração"</string>
- <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Aparece na parte superior de uma seção de conversa, em forma de balão, mostrando a foto do perfil na tela de bloqueio"</string>
+ <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Aparecem na parte superior de uma seção de conversa, em forma de balões, mostrando a foto do perfil na tela de bloqueio"</string>
<string name="notification_conversation_channel_settings" msgid="2409977688430606835">"Configurações"</string>
- <string name="notification_priority_title" msgid="2079708866333537093">"Prioridade"</string>
+ <string name="notification_priority_title" msgid="2079708866333537093">"Prioritárias"</string>
<string name="no_shortcut" msgid="8257177117568230126">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> não é compatível com recursos de conversa"</string>
<string name="bubble_overflow_empty_title" msgid="3120029421991510842">"Nenhum balão recente"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2030874469510497397">"Os balões recentes e dispensados aparecerão aqui"</string>
@@ -879,12 +879,20 @@
</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>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Posição <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Toque duas vezes para editar."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Toque duas vezes para adicionar."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Move <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Remove <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Adicionar <xliff:g id="TILE_NAME">%1$s</xliff:g> à posição <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Mover <xliff:g id="TILE_NAME">%1$s</xliff:g> para a posição <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Mantenha o botão liga/desliga pressionado para ver os novos controles"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Adicionar controles"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Editar controles"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Adicionar saídas"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositivo selecionado"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> dispositivos selecionados"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 6e32ca4b940b..93999f6b7b49 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -716,7 +716,7 @@
<string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Pode tocar ou vibrar com base nas definições do telemóvel. As conversas da app <xliff:g id="APP_NAME">%1$s</xliff:g> aparecem como um balão por predefinição."</string>
<string name="notification_channel_summary_bubble" msgid="7235935211580860537">"Mantém a sua atenção com um atalho flutuante para este conteúdo."</string>
<string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Faça com que o sistema determine se esta notificação deve emitir um som ou uma vibração"</string>
- <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Aparece na parte superior da secção de conversas, surge como um balão flutuante e apresenta a imagem do perfil no ecrã de bloqueio."</string>
+ <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Aparece no topo da secção de conversas, surge como balão flutuante e apresenta a imagem do perfil no ecrã de bloqueio."</string>
<string name="notification_conversation_channel_settings" msgid="2409977688430606835">"Definições"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prioridade"</string>
<string name="no_shortcut" msgid="8257177117568230126">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> não suporta funcionalidades de conversa."</string>
@@ -879,12 +879,20 @@
</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>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Posição <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Toque duas vezes para editar."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Toque duas vezes para adicionar."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Mover <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Remover <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Adicionar <xliff:g id="TILE_NAME">%1$s</xliff:g> à posição <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Mover <xliff:g id="TILE_NAME">%1$s</xliff:g> para a posição <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Mantenha premido o botão ligar/desligar para ver os novos controlos."</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Adicionar controlos"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Editar controlos"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Adicione saídas"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositivo selecionado"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> dispositivos selecionados"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 67f161151d47..6f96f37bc302 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -712,13 +712,13 @@
<string name="notification_automatic_title" msgid="3745465364578762652">"Automática"</string>
<string name="notification_channel_summary_low" msgid="4860617986908931158">"Som e vibração desativados"</string>
<string name="notification_conversation_summary_low" msgid="1734433426085468009">"O som e a vibração estão desativados, e o balão aparece na parte inferior da seção de conversa"</string>
- <string name="notification_channel_summary_default" msgid="3282930979307248890">"Pode vibrar ou tocar com base nas configurações do smartphone"</string>
+ <string name="notification_channel_summary_default" msgid="3282930979307248890">"Podem vibrar ou tocar com base nas configurações do smartphone"</string>
<string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Pode vibrar ou tocar com base nas configurações do smartphone. As conversas do app <xliff:g id="APP_NAME">%1$s</xliff:g> aparecem em balões por padrão."</string>
<string name="notification_channel_summary_bubble" msgid="7235935211580860537">"Mantém sua atenção com um atalho flutuante para esse conteúdo."</string>
<string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Faça com que o sistema determine se a notificação resultará em som ou vibração"</string>
- <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Aparece na parte superior de uma seção de conversa, em forma de balão, mostrando a foto do perfil na tela de bloqueio"</string>
+ <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Aparecem na parte superior de uma seção de conversa, em forma de balões, mostrando a foto do perfil na tela de bloqueio"</string>
<string name="notification_conversation_channel_settings" msgid="2409977688430606835">"Configurações"</string>
- <string name="notification_priority_title" msgid="2079708866333537093">"Prioridade"</string>
+ <string name="notification_priority_title" msgid="2079708866333537093">"Prioritárias"</string>
<string name="no_shortcut" msgid="8257177117568230126">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> não é compatível com recursos de conversa"</string>
<string name="bubble_overflow_empty_title" msgid="3120029421991510842">"Nenhum balão recente"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2030874469510497397">"Os balões recentes e dispensados aparecerão aqui"</string>
@@ -879,12 +879,20 @@
</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>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Posição <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Toque duas vezes para editar."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Toque duas vezes para adicionar."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Move <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Remove <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Adicionar <xliff:g id="TILE_NAME">%1$s</xliff:g> à posição <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Mover <xliff:g id="TILE_NAME">%1$s</xliff:g> para a posição <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Mantenha o botão liga/desliga pressionado para ver os novos controles"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Adicionar controles"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Editar controles"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Adicionar saídas"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositivo selecionado"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> dispositivos selecionados"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 691fd835b563..9810cc5540cb 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -884,12 +884,20 @@
</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>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Poziția <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Atingeți de două ori pentru a edita."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Atingeți de două ori pentru a adăuga."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Mutați <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Eliminați <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Adăugați <xliff:g id="TILE_NAME">%1$s</xliff:g> pe poziția <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Mutați <xliff:g id="TILE_NAME">%1$s</xliff:g> pe poziția <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1073,4 +1081,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Apăsați butonul de alimentare pentru a vedea noile comenzi"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Adăugați comenzi"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Editați comenzile"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Adăugați ieșiri"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grup"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"S-a selectat un dispozitiv"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"S-au selectat <xliff:g id="COUNT">%1$d</xliff:g> dispozitive"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 3ccafda2bbfe..9f43b5ac7f16 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -722,7 +722,7 @@
<string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Звонок или вибрация в зависимости от настроек телефона. Разговоры из приложения \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" по умолчанию появляются в виде всплывающего чата."</string>
<string name="notification_channel_summary_bubble" msgid="7235935211580860537">"Привлекает ваше внимание к контенту с помощью плавающего ярлыка"</string>
<string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Система будет сама определять, включать ли звуковой сигнал или вибрацию для уведомления"</string>
- <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Появляется в верхней части списка разговоров и как всплывающий чат, а также показывает фото профиля на заблокированном экране"</string>
+ <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Появляется в верхней части списка разговоров и как всплывающий чат, фото профиля показывается на заблок. экране"</string>
<string name="notification_conversation_channel_settings" msgid="2409977688430606835">"Настройки"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Приоритет"</string>
<string name="no_shortcut" msgid="8257177117568230126">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" не поддерживает функции разговоров."</string>
@@ -889,12 +889,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Показывать значки уведомлений с низким приоритетом"</string>
<string name="other" msgid="429768510980739978">"Другое"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Позиция <xliff:g id="POSITION">%1$d</xliff:g>, кнопка \"<xliff:g id="TILE_NAME">%2$s</xliff:g>\". Чтобы изменить, нажмите дважды."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"Кнопка \"<xliff:g id="TILE_NAME">%1$s</xliff:g>\". Чтобы добавить, нажмите дважды."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Переместить кнопку \"<xliff:g id="TILE_NAME">%1$s</xliff:g>\""</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Удалить кнопку \"<xliff:g id="TILE_NAME">%1$s</xliff:g>\""</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Добавить значок <xliff:g id="TILE_NAME">%1$s</xliff:g> на позицию <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Переместить значок <xliff:g id="TILE_NAME">%1$s</xliff:g> на позицию <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1079,4 +1087,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Удерживайте кнопку питания, чтобы увидеть новые элементы управления"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Добавить виджеты"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Изменить виджеты"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Добавление устройств вывода"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Группа"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Выбрано 1 устройство"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Выбрано устройств: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 14528c078a33..700f5101475c 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"අඩු ප්‍රමුඛතා දැනුම්දීම් අයිකන පෙන්වන්න"</string>
<string name="other" msgid="429768510980739978">"වෙනත්"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"ස්ථානය <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. වෙනස් කිරීමට දෙවරක් තට්ටු කරන්න."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. එක් කිරීමට දෙවරක් තට්ටු කරන්න."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"<xliff:g id="TILE_NAME">%1$s</xliff:g> ගෙන යන්න"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"<xliff:g id="TILE_NAME">%1$s</xliff:g> ඉවත් කරන්න"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"<xliff:g id="TILE_NAME">%1$s</xliff:g> තත්ත්වයට <xliff:g id="POSITION">%2$d</xliff:g> එක් කරන්න"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"<xliff:g id="TILE_NAME">%1$s</xliff:g> තත්ත්වයට <xliff:g id="POSITION">%2$d</xliff:g> ගෙන යන්න"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"නව පාලන බැලීමට බල බොත්තම අල්ලාගෙන සිටින්න"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"පාලන එක් කරන්න"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"පාලන සංස්කරණය කරන්න"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"ප්‍රතිදාන එක් කරන්න"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"සමූහය"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"උපාංග 1ක් තෝරන ලදී"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"උපාංග <xliff:g id="COUNT">%1$d</xliff:g>ක් තෝරන ලදී"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 3c405128788a..62df35aeee02 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -889,12 +889,20 @@
</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>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Pozícia <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Upravíte ju dvojitým klepnutím."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Pridáte ju dvojitým klepnutím."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Presunúť dlaždicu <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Odstrániť dlaždicu <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Pridať <xliff:g id="TILE_NAME">%1$s</xliff:g> na pozíciu <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Presunúť <xliff:g id="TILE_NAME">%1$s</xliff:g> na pozíciu <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1079,4 +1087,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Pridržaním vypínača zobrazíte nové ovládacie prvky"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Pridať ovládače"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Upraviť ovládače"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Pridanie výstupov"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Skupina"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 vybrané zariadenie"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Počet vybraných zariadení: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 4c68eeb15800..25bdd656b56c 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -95,7 +95,7 @@
<string name="screenrecord_description" msgid="1123231719680353736">"Med snemanjem lahko sistem Android zajame morebitne občutljive podatke, ki so prikazani na zaslonu ali se predvajajo v napravi. To vključuje gesla, podatke za plačilo, fotografije, sporočila in zvok."</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Snemanje zvoka"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Zvok v napravi"</string>
- <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Zvok v napravi, kot so glasba, klici in toni zvonjenja"</string>
+ <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Zvoki v napravi, kot so glasba, klici in toni zvonjenja"</string>
<string name="screenrecord_mic_label" msgid="2111264835791332350">"Mikrofon"</string>
<string name="screenrecord_device_audio_and_mic_label" msgid="1831323771978646841">"Zvok v napravi in mikrofon"</string>
<string name="screenrecord_start" msgid="330991441575775004">"Začni"</string>
@@ -889,12 +889,20 @@
</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>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Položaj <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Če želite urediti, se dvakrat dotaknite."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Če želite dodati, se dvakrat dotaknite."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Premik tega: <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Odstranitev tega: <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Dodaj ploščico <xliff:g id="TILE_NAME">%1$s</xliff:g> na položaj <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Premakni ploščico <xliff:g id="TILE_NAME">%1$s</xliff:g> na položaj <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1079,4 +1087,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Za ogled novih kontrolnikov pridržite gumb za vklop"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Dodaj kontrolnike"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Uredi kontrolnike"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Dodajanje izhodov"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Skupina"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Izbrana je ena naprava"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Izbranih je toliko naprav: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 681fa3fbeaf1..abcde4286190 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -879,12 +879,20 @@
</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>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Pozicioni <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Trokit dy herë për ta redaktuar."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Trokit dy herë për ta shtuar."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Zhvendose <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Hiqe <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Shto <xliff:g id="TILE_NAME">%1$s</xliff:g> te pozicioni <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Zhvendos <xliff:g id="TILE_NAME">%1$s</xliff:g> te pozicioni <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Mbaj shtypur butonin e energjisë për të parë kontrollet e reja"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Shto kontrollet"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Modifiko kontrollet"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Shto daljet"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupi"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 pajisje e zgjedhur"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> pajisje të zgjedhura"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 15190d4860bf..e117602afa9a 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -884,12 +884,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Прикажи иконе обавештења ниског приоритета"</string>
<string name="other" msgid="429768510980739978">"Друго"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"<xliff:g id="POSITION">%1$d</xliff:g>. позиција, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Двапут додирните да бисте изменили."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Двапут додирните да бисте додали."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Премести плочицу <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Уклони плочицу <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Додајте „<xliff:g id="TILE_NAME">%1$s</xliff:g>“ на позицију <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Преместите „<xliff:g id="TILE_NAME">%1$s</xliff:g>“ на позицију <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1073,4 +1081,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Задржите дугме за укључивање да бисте видели нове контроле"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Додај контроле"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Измени контроле"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Додајте излазе"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Група"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Изабран је 1 уређај"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Изабраних уређаја: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 6fc976ea9d36..8559b0b0de2b 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -879,12 +879,20 @@
</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>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Position <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Tryck snabbt två gånger för att redigera positionen."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Lägg till genom att trycka snabbt två gånger."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Flytta <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Ta bort <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Lägg till <xliff:g id="TILE_NAME">%1$s</xliff:g> på position <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Flytta <xliff:g id="TILE_NAME">%1$s</xliff:g> till position <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"De nya snabbkontrollerna visas om du håller strömbrytaren nedtryckt"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Lägg till snabbkontroller"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Redigera snabbkontroller"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Lägg till utgångar"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupp"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 enhet har valts"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> enheter har valts"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 67d5f3404269..76b86eee8289 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Onyesha aikoni za arifa zisizo muhimu"</string>
<string name="other" msgid="429768510980739978">"Nyingine"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Nafasi ya <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Gusa mara mbili ili ubadilishe."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Gusa mara mbili ili uongeze."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Hamisha <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Ondoa <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Ongeza <xliff:g id="TILE_NAME">%1$s</xliff:g> kwenye nafasi ya <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Sogeza <xliff:g id="TILE_NAME">%1$s</xliff:g> kwenye nafasi ya <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Shikilia kitufe cha kuwasha/kuzima ili uone vidhibiti vipya"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Weka vidhibiti"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Badilisha vidhibiti"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Weka vifaa vya kutoa sauti"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Kikundi"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Umechagua kifaa 1"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Umechagua vifaa <xliff:g id="COUNT">%1$d</xliff:g>"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 8f2e296301d7..9d5c54fc7890 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"குறைந்த முன்னுரிமை உள்ள அறிவிப்பு ஐகான்களைக் காட்டு"</string>
<string name="other" msgid="429768510980739978">"மற்றவை"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"நிலை <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. திருத்த, இருமுறை தட்டவும்."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. சேர்க்க, இருமுறை தட்டவும்."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"<xliff:g id="TILE_NAME">%1$s</xliff:g>ஐ நகர்த்தவும்"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"<xliff:g id="TILE_NAME">%1$s</xliff:g>ஐ அகற்றவும்"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"நிலைப்பாடு <xliff:g id="POSITION">%2$d</xliff:g> இல் <xliff:g id="TILE_NAME">%1$s</xliff:g>ஐச் சேர்க்கும்"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"நிலைப்பாடு <xliff:g id="POSITION">%2$d</xliff:g>க்கு <xliff:g id="TILE_NAME">%1$s</xliff:g>ஐ நகர்த்தும்"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"புதிய கட்டுப்பாடுகளைப் பார்க்க பவர் பட்டனைப் பிடித்திருக்கவும்"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"கட்டுப்பாடுகளைச் சேர்த்தல்"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"கட்டுப்பாடுகளை மாற்றுதல்"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"அவுட்புட்களைச் சேர்த்தல்"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"குழு"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 சாதனம் தேர்ந்தெடுக்கப்பட்டுள்ளது"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> சாதனங்கள் தேர்ந்தெடுக்கப்பட்டுள்ளன"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 8a15d7615140..925c958199ed 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -693,7 +693,7 @@
<string name="notification_channel_unsilenced" msgid="94878840742161152">"ఈ నోటిఫికేషన్‌లు మిమ్మల్ని హెచ్చరిస్తాయి"</string>
<string name="inline_blocking_helper" msgid="2891486013649543452">"మీరు సాధారణంగా ఈ నోటిఫికేషన్‌లను విస్మరిస్తారు. \nవాటి ప్రదర్శనను కొనసాగించాలా?"</string>
<string name="inline_done_button" msgid="6043094985588909584">"పూర్తయింది"</string>
- <string name="inline_ok_button" msgid="603075490581280343">"అప్లై చేయి"</string>
+ <string name="inline_ok_button" msgid="603075490581280343">"అప్లయి చేయి"</string>
<string name="inline_keep_showing" msgid="8736001253507073497">"ఈ నోటిఫికేషన్‌లను చూపిస్తూ ఉండాలా?"</string>
<string name="inline_stop_button" msgid="2453460935438696090">"నోటిఫికేషన్‌లను ఆపివేయి"</string>
<string name="inline_deliver_silently_button" msgid="2714314213321223286">"నిశ్శబ్దంగా బట్వాడా చేయండి"</string>
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"తక్కువ ప్రాధాన్యత నోటిఫికేషన్ చిహ్నాలను చూపించు"</string>
<string name="other" msgid="429768510980739978">"ఇతరం"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"స్థానం <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. సవరించడానికి రెండుసార్లు నొక్కండి."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. జోడించడానికి రెండుసార్లు నొక్కండి."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"<xliff:g id="TILE_NAME">%1$s</xliff:g>ని తరలిస్తుంది"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"<xliff:g id="TILE_NAME">%1$s</xliff:g>ని తీసివేస్తుంది"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"స్థానం <xliff:g id="POSITION">%2$d</xliff:g>కి <xliff:g id="TILE_NAME">%1$s</xliff:g>ని జోడించండి"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"స్థానం <xliff:g id="POSITION">%2$d</xliff:g>కి <xliff:g id="TILE_NAME">%1$s</xliff:g>ని తరలించండి"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"కొత్త నియంత్రణలను చూడడానికి పవర్ బటన్‌ని నొక్కి పట్టుకోండి"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"నియంత్రణలను జోడించండి"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"నియంత్రణలను ఎడిట్ చేయండి"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"అవుట్‌పుట్‌లను జోడించండి"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"గ్రూప్"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 పరికరం ఎంచుకోబడింది"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> పరికరాలు ఎంచుకోబడ్డాయి"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 16593b4c245f..c613e9c0d7c0 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -92,7 +92,7 @@
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"กำลังประมวลผลการอัดหน้าจอ"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"การแจ้งเตือนต่อเนื่องสำหรับเซสชันการบันทึกหน้าจอ"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"เริ่มบันทึกเลยไหม"</string>
- <string name="screenrecord_description" msgid="1123231719680353736">"ขณะบันทึก ระบบ Android อาจบันทึกข้อมูลที่ละเอียดอ่อนที่ปรากฏบนหน้าจอหรือเล่นในอุปกรณ์ได้ ซึ่งรวมถึงรหัสผ่าน ข้อมูลการชำระเงิน รูปภาพ ข้อความ และเสียง"</string>
+ <string name="screenrecord_description" msgid="1123231719680353736">"ขณะบันทึก ระบบ Android อาจบันทึกข้อมูลที่ละเอียดอ่อนซึ่งปรากฏบนหน้าจอหรือเล่นในอุปกรณ์ได้ ซึ่งรวมถึงรหัสผ่าน ข้อมูลการชำระเงิน รูปภาพ ข้อความ และเสียง"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"บันทึกเสียง"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"เสียงจากอุปกรณ์"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"เสียงจากอุปกรณ์ เช่น เพลง การโทร และเสียงเรียกเข้า"</string>
@@ -337,7 +337,7 @@
<string name="accessibility_rotation_lock_on_landscape_changed" msgid="5785739044300729592">"ขณะนี้หน้าจอล็อกอยู่ในแนวนอน"</string>
<string name="accessibility_rotation_lock_on_portrait_changed" msgid="5580170829728987989">"ขณะนี้หน้าจอล็อกอยู่ในแนวตั้ง"</string>
<string name="dessert_case" msgid="9104973640704357717">"ชั้นแสดงของหวาน"</string>
- <string name="start_dreams" msgid="9131802557946276718">"โปรแกรมรักษาหน้าจอ"</string>
+ <string name="start_dreams" msgid="9131802557946276718">"โปรแกรมรักษาจอภาพ"</string>
<string name="ethernet_label" msgid="2203544727007463351">"อีเทอร์เน็ต"</string>
<string name="quick_settings_header_onboarding_text" msgid="1918085351115504765">"แตะไอคอนค้างไว้เพื่อดูตัวเลือกอื่นๆ"</string>
<string name="quick_settings_dnd_label" msgid="7728690179108024338">"ห้ามรบกวน"</string>
@@ -509,7 +509,7 @@
<string name="manage_notifications_text" msgid="6885645344647733116">"จัดการ"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ประวัติ"</string>
<string name="notification_section_header_incoming" msgid="850925217908095197">"ใหม่"</string>
- <string name="notification_section_header_gentle" msgid="6804099527336337197">"เงียบ"</string>
+ <string name="notification_section_header_gentle" msgid="6804099527336337197">"ปิดเสียง"</string>
<string name="notification_section_header_alerting" msgid="5581175033680477651">"การแจ้งเตือน"</string>
<string name="notification_section_header_conversations" msgid="821834744538345661">"การสนทนา"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"ล้างการแจ้งเตือนแบบไม่มีเสียงทั้งหมด"</string>
@@ -706,7 +706,7 @@
<string name="inline_silent_button_keep_alerting" msgid="6577845442184724992">"แจ้งเตือนต่อไป"</string>
<string name="inline_turn_off_notifications" msgid="8543989584403106071">"ปิดการแจ้งเตือน"</string>
<string name="inline_keep_showing_app" msgid="4393429060390649757">"แสดงการแจ้งเตือนจากแอปนี้ต่อไปไหม"</string>
- <string name="notification_silence_title" msgid="8608090968400832335">"เงียบ"</string>
+ <string name="notification_silence_title" msgid="8608090968400832335">"ปิดเสียง"</string>
<string name="notification_alert_title" msgid="3656229781017543655">"ค่าเริ่มต้น"</string>
<string name="notification_bubble_title" msgid="8330481035191903164">"บับเบิล"</string>
<string name="notification_automatic_title" msgid="3745465364578762652">"อัตโนมัติ"</string>
@@ -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,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"แสดงไอคอนการแจ้งเตือนลำดับความสำคัญต่ำ"</string>
<string name="other" msgid="429768510980739978">"อื่นๆ"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"ตำแหน่ง <xliff:g id="POSITION">%1$d</xliff:g> <xliff:g id="TILE_NAME">%2$s</xliff:g> แตะ 2 ครั้งเพื่อแก้ไข"</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g> แตะ 2 ครั้งเพื่อเพิ่ม"</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"ย้าย <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"นำ <xliff:g id="TILE_NAME">%1$s</xliff:g> ออก"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"เพิ่ม <xliff:g id="TILE_NAME">%1$s</xliff:g> ไปยังตำแหน่ง <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"ย้าย <xliff:g id="TILE_NAME">%1$s</xliff:g> ไปยังตำแหน่ง <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"กดปุ่มเปิด/ปิดค้างไว้เพื่อดูตัวควบคุมใหม่ๆ"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"เพิ่มตัวควบคุม"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"แก้ไขตัวควบคุม"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"เพิ่มเอาต์พุต"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"กลุ่ม"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"เลือกอุปกรณ์ไว้ 1 รายการ"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"เลือกอุปกรณ์ไว้ <xliff:g id="COUNT">%1$d</xliff:g> รายการ"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 283a6d54ef0f..9411957e56f2 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -879,12 +879,20 @@
</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>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Posisyon <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. I-double tap upang i-edit."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. I-double tap upang idagdag."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Ilipat ang <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Alisin ang <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Idagdag ang <xliff:g id="TILE_NAME">%1$s</xliff:g> sa posisyong <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Ilipat ang <xliff:g id="TILE_NAME">%1$s</xliff:g> sa posisyong <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Pindutin nang matagal ang Power button para makita ang mga bagong kontrol"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Magdagdag ng mga kontrol"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Mag-edit ng mga kontrol"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Magdagdag ng mga output"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 device ang napili"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> (na) device ang napili"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 514f4ee57ce3..4d46f93e2d25 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -879,12 +879,20 @@
</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>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"<xliff:g id="POSITION">%1$d</xliff:g>. konum, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Düzenlemek için iki kez dokunun."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Eklemek için iki kez dokunun."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"<xliff:g id="TILE_NAME">%1$s</xliff:g> kutusunu taşı"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"<xliff:g id="TILE_NAME">%1$s</xliff:g> kutusunu kaldır"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"<xliff:g id="TILE_NAME">%1$s</xliff:g> öğesini <xliff:g id="POSITION">%2$d</xliff:g> konumuna ekle"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"<xliff:g id="TILE_NAME">%1$s</xliff:g> öğesini <xliff:g id="POSITION">%2$d</xliff:g> konumuna taşı"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Yeni kontrolleri görmek için Güç düğmesini basılı tutun"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Denetim ekle"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Denetimleri düzenle"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Çıkışlar ekleyin"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grup"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 cihaz seçildi"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> cihaz seçildi"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 565907ce9106..399b53a91de6 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -889,12 +889,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Показувати значки сповіщень із низьким пріоритетом"</string>
<string name="other" msgid="429768510980739978">"Інше"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Позиція <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Двічі торкніться, щоб змінити."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Двічі торкніться, щоб додати."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Перемістити <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Видалити <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Додати <xliff:g id="TILE_NAME">%1$s</xliff:g> на позицію <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Перемістити <xliff:g id="TILE_NAME">%1$s</xliff:g> на позицію <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1079,4 +1087,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Утримуйте кнопку живлення, щоб переглянути нові елементи керування"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Додати елементи керування"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Змінити елементи керування"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Додати пристрої виводу"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Група"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Вибрано 1 пристрій"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Вибрано пристроїв: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index d5f90195a4d9..09e500d227a6 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"کم ترجیحی اطلاع کے آئیکنز دکھائیں"</string>
<string name="other" msgid="429768510980739978">"دیگر"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"پوزیشن <xliff:g id="POSITION">%1$d</xliff:g>، <xliff:g id="TILE_NAME">%2$s</xliff:g>۔ ترمیم کرنے کیلئے دو بار تھپتھپائیں۔"</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>۔ شامل کرنے کیلئے دو بار تھپتھپائیں۔"</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"<xliff:g id="TILE_NAME">%1$s</xliff:g> کو منتقل کریں"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"<xliff:g id="TILE_NAME">%1$s</xliff:g> ہٹائیں"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"پوزیشن <xliff:g id="TILE_NAME">%1$s</xliff:g> میں <xliff:g id="POSITION">%2$d</xliff:g> شامل کریں"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"<xliff:g id="TILE_NAME">%1$s</xliff:g> کو پوزیشن <xliff:g id="POSITION">%2$d</xliff:g> میں منتقل کریں"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"نئے کنٹرولز دیکھنے کے لیے پاور بٹن کو دبائے رکھیں"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"کنٹرولز شامل کریں"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"کنٹرولز میں ترمیم کریں"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"آؤٹ پٹس شامل کریں"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"گروپ"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 آلہ منتخب کیا گیا"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> آلات منتخب کیے گئے"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index 71703e05ea29..61b91c768582 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Muhim boʻlmagan bildirishnoma ikonkalarini koʻrsatish"</string>
<string name="other" msgid="429768510980739978">"Boshqa"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"<xliff:g id="POSITION">%1$d</xliff:g>-joy, “<xliff:g id="TILE_NAME">%2$s</xliff:g>” tugmasi. Tahrirlash uchun ustiga ikki marta bosing."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"“<xliff:g id="TILE_NAME">%1$s</xliff:g>” tugmasi. Qo‘shish uchun ustiga ikki marta bosing."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"“<xliff:g id="TILE_NAME">%1$s</xliff:g>” tugmasini ko‘chirish"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"<xliff:g id="TILE_NAME">%1$s</xliff:g> tugmasini olib tashlash"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"<xliff:g id="POSITION">%2$d</xliff:g>-joyga buni qo‘shish: <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"<xliff:g id="POSITION">%2$d</xliff:g>-joyga buni ko‘chirish: <xliff:g id="TILE_NAME">%1$s</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Yangi boshqaruv elementlari bilan tanishish uchun quvvat tugmasini bosib turing"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Element kiritish"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Elementlarni tahrirlash"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Chiquvchi qurilmani kiritish"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Guruh"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 ta qurilma tanlandi"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> ta qurilma tanlandi"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 12eb5e12f2a6..dc2a5b826439 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -879,12 +879,20 @@
</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>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Vị trí <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Nhấn đúp để chỉnh sửa."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Nhấn đúp để thêm."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Di chuyển <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Xóa <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Thêm <xliff:g id="TILE_NAME">%1$s</xliff:g> vào vị trí <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Di chuyển <xliff:g id="TILE_NAME">%1$s</xliff:g> đến vị trí <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Giữ nút Nguồn để xem các tùy chọn điều khiển mới"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Thêm các tùy chọn điều khiển"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Chỉnh sửa tùy chọn điều khiển"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Thêm thiết bị đầu ra"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Nhóm"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Đã chọn 1 thiết bị"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Đã chọn <xliff:g id="COUNT">%1$d</xliff:g> thiết bị"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index f886ecb6cf39..71a7d8e7165d 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"显示低优先级的通知图标"</string>
<string name="other" msgid="429768510980739978">"其他"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"位置 <xliff:g id="POSITION">%1$d</xliff:g>,<xliff:g id="TILE_NAME">%2$s</xliff:g>。点按两次即可修改。"</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>。点按两次即可添加。"</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"移动<xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"移除<xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"将“<xliff:g id="TILE_NAME">%1$s</xliff:g>”添加到位置 <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"将“<xliff:g id="TILE_NAME">%1$s</xliff:g>”移动到位置 <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"按住电源按钮即可查看新控件"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"添加控件"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"修改控件"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"添加输出设备"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"群组"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"已选择 1 个设备"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"已选择 <xliff:g id="COUNT">%1$d</xliff:g> 个设备"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 744d341deb08..1d763d010984 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -716,7 +716,7 @@
<string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"可能會根據手機設定發出鈴聲或震動。「<xliff:g id="APP_NAME">%1$s</xliff:g>」的對話會預設以對話氣泡顯示。"</string>
<string name="notification_channel_summary_bubble" msgid="7235935211580860537">"為此內容建立浮動捷徑以保持注意力。"</string>
<string name="notification_channel_summary_automatic" msgid="5813109268050235275">"由系統判斷是否要讓此通知發出音效或震動"</string>
- <string name="notification_channel_summary_priority" msgid="7952654515769021553">"在對話部分的頂部以浮動對話氣泡顯示,並在上鎖畫面顯示個人檔案相片"</string>
+ <string name="notification_channel_summary_priority" msgid="7952654515769021553">"以浮動對話泡顯示在對話部分的頂部,並在上鎖畫面顯示個人檔案相片"</string>
<string name="notification_conversation_channel_settings" msgid="2409977688430606835">"設定"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"重要"</string>
<string name="no_shortcut" msgid="8257177117568230126">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」不支援對話功能"</string>
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"顯示低優先順序通知圖示"</string>
<string name="other" msgid="429768510980739978">"其他"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"位置 <xliff:g id="POSITION">%1$d</xliff:g>,<xliff:g id="TILE_NAME">%2$s</xliff:g>。輕按兩下即可編輯。"</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>。輕按兩下即可新增。"</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"移動 <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"移除 <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"將「<xliff:g id="TILE_NAME">%1$s</xliff:g>」加去位置 <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"將「<xliff:g id="TILE_NAME">%1$s</xliff:g>」移去位置 <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"按住「開關」按鈕以查看新控制項"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"新增控制項"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"編輯控制項"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"新增輸出裝置"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"群組"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"已選取 1 部裝置"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"已選取 <xliff:g id="COUNT">%1$d</xliff:g> 部裝置"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 49d530f0a7af..303ddd925a86 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -711,7 +711,7 @@
<string name="notification_bubble_title" msgid="8330481035191903164">"泡泡"</string>
<string name="notification_automatic_title" msgid="3745465364578762652">"自動"</string>
<string name="notification_channel_summary_low" msgid="4860617986908931158">"不震動或發出聲音"</string>
- <string name="notification_conversation_summary_low" msgid="1734433426085468009">"不震動或發出聲音,並顯示在對話部分的下方"</string>
+ <string name="notification_conversation_summary_low" msgid="1734433426085468009">"不震動或發出聲音,並調整排序到其他對話下方"</string>
<string name="notification_channel_summary_default" msgid="3282930979307248890">"根據手機的設定響鈴或震動"</string>
<string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"可能會根據手機的設定響鈴或震動。根據預設,來自「<xliff:g id="APP_NAME">%1$s</xliff:g>」的對話會以對話框形式顯示。"</string>
<string name="notification_channel_summary_bubble" msgid="7235935211580860537">"利用浮動式捷徑快速存取這項內容。"</string>
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"顯示低優先順序通知圖示"</string>
<string name="other" msgid="429768510980739978">"其他"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"位置 <xliff:g id="POSITION">%1$d</xliff:g>,<xliff:g id="TILE_NAME">%2$s</xliff:g>。輕觸兩下即可編輯。"</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>。輕觸兩下即可新增。"</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"移動 <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"移除 <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"將 <xliff:g id="TILE_NAME">%1$s</xliff:g> 新增到位置 <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"將 <xliff:g id="TILE_NAME">%1$s</xliff:g> 移到位置 <xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"按住電源按鈕即可查看新的控制項"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"新增控制項"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"編輯控制項"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"新增輸出裝置"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"群組"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"已選取 1 部裝置"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"已選取 <xliff:g id="COUNT">%1$d</xliff:g> 部裝置"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index abc45c84ccd0..d8eb7ade9600 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -879,12 +879,20 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Bonisa izithonjana zesaziso zokubaluleka okuncane"</string>
<string name="other" msgid="429768510980739978">"Okunye"</string>
- <string name="accessibility_qs_edit_tile_label" msgid="9079791448815232967">"Isimo esingu-<xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Thepha kabili ukuze uhlele."</string>
- <string name="accessibility_qs_edit_add_tile_label" msgid="8292218072049068613">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Thepha kabili ukuze ungeze."</string>
- <string name="accessibility_qs_edit_move_tile" msgid="6027997446473163426">"Hambisa i-<xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_remove_tile" msgid="3406781901949899624">"Susa i-<xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_add" msgid="6289879620154587233">"Engeza i-<xliff:g id="TILE_NAME">%1$s</xliff:g> ukuze ubeke i-<xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_move" msgid="4841770637244326837">"Hambisa i-<xliff:g id="TILE_NAME">%1$s</xliff:g> ukuze ubeke i-<xliff:g id="POSITION">%2$d</xliff:g>"</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_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>
@@ -1067,4 +1075,11 @@
<string name="controls_added_tooltip" msgid="4842812921719153085">"Bamba Inkinobho yamandla ukuze ubone izilawuli ezintsha"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Engeza Izilawuli"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Hlela izilawuli"</string>
+ <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Engeza okukhiphayo"</string>
+ <string name="media_output_dialog_group" msgid="5571251347877452212">"Iqembu"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"idivayisi ekhethiwe e-1"</string>
+ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"amadivayisi akhethiwe angu-<xliff:g id="COUNT">%1$d</xliff:g>"</string>
+ <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>
</resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
index e99245fa438f..23195af8bdea 100644
--- a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
@@ -33,9 +33,13 @@ import android.view.SurfaceView;
import android.view.ViewGroup;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.keyguard.dagger.KeyguardBouncerScope;
+import com.android.systemui.dagger.qualifiers.Main;
import java.util.NoSuchElementException;
+import javax.inject.Inject;
+
/**
* Encapsulates all logic for secondary lockscreen state management.
*/
@@ -142,9 +146,9 @@ public class AdminSecondaryLockScreenController {
}
};
- public AdminSecondaryLockScreenController(Context context, ViewGroup parent,
+ private AdminSecondaryLockScreenController(Context context, KeyguardSecurityContainer parent,
KeyguardUpdateMonitor updateMonitor, KeyguardSecurityCallback callback,
- Handler handler) {
+ @Main Handler handler) {
mContext = context;
mHandler = handler;
mParent = parent;
@@ -234,4 +238,26 @@ public class AdminSecondaryLockScreenController {
getHolder().removeCallback(mSurfaceHolderCallback);
}
}
+
+ @KeyguardBouncerScope
+ public static class Factory {
+ private final Context mContext;
+ private final KeyguardSecurityContainer mParent;
+ private final KeyguardUpdateMonitor mUpdateMonitor;
+ private final Handler mHandler;
+
+ @Inject
+ public Factory(Context context, KeyguardSecurityContainer parent,
+ KeyguardUpdateMonitor updateMonitor, @Main Handler handler) {
+ mContext = context;
+ mParent = parent;
+ mUpdateMonitor = updateMonitor;
+ mHandler = handler;
+ }
+
+ public AdminSecondaryLockScreenController create(KeyguardSecurityCallback callback) {
+ return new AdminSecondaryLockScreenController(mContext, mParent, mUpdateMonitor,
+ callback, mHandler);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
index 88f4176f5eac..cc6df45c598f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
@@ -16,46 +16,26 @@
package com.android.keyguard;
-import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL;
-import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED;
-
import android.content.Context;
-import android.content.res.ColorStateList;
-import android.os.AsyncTask;
-import android.os.CountDownTimer;
-import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.View;
-import android.widget.LinearLayout;
-import com.android.internal.util.LatencyTracker;
-import com.android.internal.widget.LockPatternChecker;
-import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
/**
* Base class for PIN and password unlock screens.
*/
-public abstract class KeyguardAbsKeyInputView extends LinearLayout
- implements KeyguardSecurityView, EmergencyButton.EmergencyButtonCallback {
- protected KeyguardSecurityCallback mCallback;
- protected LockPatternUtils mLockPatternUtils;
- protected AsyncTask<?, ?, ?> mPendingLockCheck;
- protected SecurityMessageDisplay mSecurityMessageDisplay;
+public abstract class KeyguardAbsKeyInputView extends KeyguardInputView {
protected View mEcaView;
protected boolean mEnableHaptics;
- private boolean mDismissing;
- protected boolean mResumed;
- private CountDownTimer mCountdownTimer = null;
- private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
// To avoid accidental lockout due to events while the device in in the pocket, ignore
// any passwords with length less than or equal to this length.
protected static final int MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT = 3;
+ private KeyDownListener mKeyDownListener;
public KeyguardAbsKeyInputView(Context context) {
this(context, null);
@@ -63,38 +43,10 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout
public KeyguardAbsKeyInputView(Context context, AttributeSet attrs) {
super(context, attrs);
- mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
}
- @Override
- public void setKeyguardCallback(KeyguardSecurityCallback callback) {
- mCallback = callback;
- }
-
- @Override
- public void setLockPatternUtils(LockPatternUtils utils) {
- mLockPatternUtils = utils;
- mEnableHaptics = mLockPatternUtils.isTactileFeedbackEnabled();
- }
-
- @Override
- public void reset() {
- // start fresh
- mDismissing = false;
- resetPasswordText(false /* animate */, false /* announce */);
- // if the user is currently locked out, enforce it.
- long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
- KeyguardUpdateMonitor.getCurrentUser());
- if (shouldLockout(deadline)) {
- handleAttemptLockout(deadline);
- } else {
- resetState();
- }
- }
-
- // Allow subclasses to override this behavior
- protected boolean shouldLockout(long deadline) {
- return deadline != 0;
+ void setEnableHaptics(boolean enableHaptics) {
+ mEnableHaptics = enableHaptics;
}
protected abstract int getPasswordTextViewId();
@@ -102,24 +54,7 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout
@Override
protected void onFinishInflate() {
- mLockPatternUtils = new LockPatternUtils(mContext);
mEcaView = findViewById(R.id.keyguard_selector_fade_container);
-
- EmergencyButton button = findViewById(R.id.emergency_call_button);
- if (button != null) {
- button.setCallback(this);
- }
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mSecurityMessageDisplay = KeyguardMessageArea.findSecurityMessageDisplay(this);
- }
-
- @Override
- public void onEmergencyButtonClickedWhenInCall() {
- mCallback.reset();
}
/*
@@ -131,195 +66,14 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout
return R.string.kg_wrong_password;
}
- protected void verifyPasswordAndUnlock() {
- if (mDismissing) return; // already verified but haven't been dismissed; don't do it again.
-
- final LockscreenCredential password = getEnteredCredential();
- setPasswordEntryInputEnabled(false);
- if (mPendingLockCheck != null) {
- mPendingLockCheck.cancel(false);
- }
-
- final int userId = KeyguardUpdateMonitor.getCurrentUser();
- if (password.size() <= MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT) {
- // to avoid accidental lockout, only count attempts that are long enough to be a
- // real password. This may require some tweaking.
- setPasswordEntryInputEnabled(true);
- onPasswordChecked(userId, false /* matched */, 0, false /* not valid - too short */);
- password.zeroize();
- return;
- }
-
- if (LatencyTracker.isEnabled(mContext)) {
- LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL);
- LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED);
- }
-
- mKeyguardUpdateMonitor.setCredentialAttempted();
- mPendingLockCheck = LockPatternChecker.checkCredential(
- mLockPatternUtils,
- password,
- userId,
- new LockPatternChecker.OnCheckCallback() {
-
- @Override
- public void onEarlyMatched() {
- if (LatencyTracker.isEnabled(mContext)) {
- LatencyTracker.getInstance(mContext).onActionEnd(
- ACTION_CHECK_CREDENTIAL);
- }
- onPasswordChecked(userId, true /* matched */, 0 /* timeoutMs */,
- true /* isValidPassword */);
- password.zeroize();
- }
-
- @Override
- public void onChecked(boolean matched, int timeoutMs) {
- if (LatencyTracker.isEnabled(mContext)) {
- LatencyTracker.getInstance(mContext).onActionEnd(
- ACTION_CHECK_CREDENTIAL_UNLOCKED);
- }
- setPasswordEntryInputEnabled(true);
- mPendingLockCheck = null;
- if (!matched) {
- onPasswordChecked(userId, false /* matched */, timeoutMs,
- true /* isValidPassword */);
- }
- password.zeroize();
- }
-
- @Override
- public void onCancelled() {
- // We already got dismissed with the early matched callback, so we cancelled
- // the check. However, we still need to note down the latency.
- if (LatencyTracker.isEnabled(mContext)) {
- LatencyTracker.getInstance(mContext).onActionEnd(
- ACTION_CHECK_CREDENTIAL_UNLOCKED);
- }
- password.zeroize();
- }
- });
- }
-
- private void onPasswordChecked(int userId, boolean matched, int timeoutMs,
- boolean isValidPassword) {
- boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId;
- if (matched) {
- mCallback.reportUnlockAttempt(userId, true, 0);
- if (dismissKeyguard) {
- mDismissing = true;
- mCallback.dismiss(true, userId);
- }
- } else {
- if (isValidPassword) {
- mCallback.reportUnlockAttempt(userId, false, timeoutMs);
- if (timeoutMs > 0) {
- long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
- userId, timeoutMs);
- handleAttemptLockout(deadline);
- }
- }
- if (timeoutMs == 0) {
- mSecurityMessageDisplay.setMessage(getWrongPasswordStringId());
- }
- }
- resetPasswordText(true /* animate */, !matched /* announce deletion if no match */);
- }
-
protected abstract void resetPasswordText(boolean animate, boolean announce);
protected abstract LockscreenCredential getEnteredCredential();
protected abstract void setPasswordEntryEnabled(boolean enabled);
protected abstract void setPasswordEntryInputEnabled(boolean enabled);
- // Prevent user from using the PIN/Password entry until scheduled deadline.
- protected void handleAttemptLockout(long elapsedRealtimeDeadline) {
- setPasswordEntryEnabled(false);
- long elapsedRealtime = SystemClock.elapsedRealtime();
- long secondsInFuture = (long) Math.ceil(
- (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0);
- mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) {
-
- @Override
- public void onTick(long millisUntilFinished) {
- int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0);
- mSecurityMessageDisplay.setMessage(mContext.getResources().getQuantityString(
- R.plurals.kg_too_many_failed_attempts_countdown,
- secondsRemaining, secondsRemaining));
- }
-
- @Override
- public void onFinish() {
- mSecurityMessageDisplay.setMessage("");
- resetState();
- }
- }.start();
- }
-
- protected void onUserInput() {
- if (mCallback != null) {
- mCallback.userActivity();
- mCallback.onUserInput();
- }
- mSecurityMessageDisplay.setMessage("");
- }
-
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
- // Fingerprint sensor sends a KeyEvent.KEYCODE_UNKNOWN.
- // We don't want to consider it valid user input because the UI
- // will already respond to the event.
- if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
- onUserInput();
- }
- return false;
- }
-
- @Override
- public boolean needsInput() {
- return false;
- }
-
- @Override
- public void onPause() {
- mResumed = false;
-
- if (mCountdownTimer != null) {
- mCountdownTimer.cancel();
- mCountdownTimer = null;
- }
- if (mPendingLockCheck != null) {
- mPendingLockCheck.cancel(false);
- mPendingLockCheck = null;
- }
- reset();
- }
-
- @Override
- public void onResume(int reason) {
- mResumed = true;
- }
-
- @Override
- public KeyguardSecurityCallback getCallback() {
- return mCallback;
- }
-
- @Override
- public void showPromptReason(int reason) {
- if (reason != PROMPT_REASON_NONE) {
- int promtReasonStringRes = getPromptReasonStringRes(reason);
- if (promtReasonStringRes != 0) {
- mSecurityMessageDisplay.setMessage(promtReasonStringRes);
- }
- }
- }
-
- @Override
- public void showMessage(CharSequence message, ColorStateList colorState) {
- if (colorState != null) {
- mSecurityMessageDisplay.setNextMessageColor(colorState);
- }
- mSecurityMessageDisplay.setMessage(message);
+ return mKeyDownListener != null && mKeyDownListener.onKeyDown(keyCode, event);
}
protected abstract int getPromptReasonStringRes(int reason);
@@ -333,9 +87,12 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout
}
}
- @Override
- public boolean startDisappearAnimation(Runnable finishRunnable) {
- return false;
+ public void setKeyDownListener(KeyDownListener keyDownListener) {
+ mKeyDownListener = keyDownListener;
+ }
+
+ public interface KeyDownListener {
+ boolean onKeyDown(int keyCode, KeyEvent keyEvent);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
new file mode 100644
index 000000000000..d957628c52ab
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.keyguard;
+
+import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL;
+import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED;
+import static com.android.keyguard.KeyguardAbsKeyInputView.MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT;
+
+import android.content.res.ColorStateList;
+import android.os.AsyncTask;
+import android.os.CountDownTimer;
+import android.os.SystemClock;
+import android.view.KeyEvent;
+
+import com.android.internal.util.LatencyTracker;
+import com.android.internal.widget.LockPatternChecker;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
+import com.android.keyguard.EmergencyButton.EmergencyButtonCallback;
+import com.android.keyguard.KeyguardAbsKeyInputView.KeyDownListener;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.R;
+
+public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKeyInputView>
+ extends KeyguardInputViewController<T> {
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final LockPatternUtils mLockPatternUtils;
+ private final LatencyTracker mLatencyTracker;
+ private CountDownTimer mCountdownTimer;
+ protected KeyguardMessageAreaController mMessageAreaController;
+ private boolean mDismissing;
+ protected AsyncTask<?, ?, ?> mPendingLockCheck;
+ protected boolean mResumed;
+
+ private final KeyDownListener mKeyDownListener = (keyCode, keyEvent) -> {
+ // Fingerprint sensor sends a KeyEvent.KEYCODE_UNKNOWN.
+ // We don't want to consider it valid user input because the UI
+ // will already respond to the event.
+ if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
+ onUserInput();
+ }
+ return false;
+ };
+
+ private final EmergencyButtonCallback mEmergencyButtonCallback = new EmergencyButtonCallback() {
+ @Override
+ public void onEmergencyButtonClickedWhenInCall() {
+ getKeyguardSecurityCallback().reset();
+ }
+ };
+
+ protected KeyguardAbsKeyInputViewController(T view,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ SecurityMode securityMode,
+ LockPatternUtils lockPatternUtils,
+ KeyguardSecurityCallback keyguardSecurityCallback,
+ KeyguardMessageAreaController.Factory messageAreaControllerFactory,
+ LatencyTracker latencyTracker) {
+ super(view, securityMode, keyguardSecurityCallback);
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mLockPatternUtils = lockPatternUtils;
+ mLatencyTracker = latencyTracker;
+ KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView);
+ mMessageAreaController = messageAreaControllerFactory.create(kma);
+ }
+
+ abstract void resetState();
+
+ @Override
+ public void init() {
+ super.init();
+ mMessageAreaController.init();
+ }
+
+ @Override
+ protected void onViewAttached() {
+ mView.setKeyDownListener(mKeyDownListener);
+ mView.setEnableHaptics(mLockPatternUtils.isTactileFeedbackEnabled());
+ EmergencyButton button = mView.findViewById(R.id.emergency_call_button);
+ if (button != null) {
+ button.setCallback(mEmergencyButtonCallback);
+ }
+ }
+
+ @Override
+ public void reset() {
+ // start fresh
+ mDismissing = false;
+ mView.resetPasswordText(false /* animate */, false /* announce */);
+ // if the user is currently locked out, enforce it.
+ long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
+ KeyguardUpdateMonitor.getCurrentUser());
+ if (shouldLockout(deadline)) {
+ handleAttemptLockout(deadline);
+ } else {
+ mView.resetState();
+ }
+ }
+
+ @Override
+ public boolean needsInput() {
+ return false;
+ }
+
+ @Override
+ public void showMessage(CharSequence message, ColorStateList colorState) {
+ if (colorState != null) {
+ mMessageAreaController.setNextMessageColor(colorState);
+ }
+ mMessageAreaController.setMessage(message);
+ }
+
+ // Allow subclasses to override this behavior
+ protected boolean shouldLockout(long deadline) {
+ return deadline != 0;
+ }
+
+ // Prevent user from using the PIN/Password entry until scheduled deadline.
+ protected void handleAttemptLockout(long elapsedRealtimeDeadline) {
+ mView.setPasswordEntryEnabled(false);
+ long elapsedRealtime = SystemClock.elapsedRealtime();
+ long secondsInFuture = (long) Math.ceil(
+ (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0);
+ mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) {
+
+ @Override
+ public void onTick(long millisUntilFinished) {
+ int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0);
+ mMessageAreaController.setMessage(mView.getResources().getQuantityString(
+ R.plurals.kg_too_many_failed_attempts_countdown,
+ secondsRemaining, secondsRemaining));
+ }
+
+ @Override
+ public void onFinish() {
+ mMessageAreaController.setMessage("");
+ resetState();
+ }
+ }.start();
+ }
+
+ void onPasswordChecked(int userId, boolean matched, int timeoutMs, boolean isValidPassword) {
+ boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId;
+ if (matched) {
+ getKeyguardSecurityCallback().reportUnlockAttempt(userId, true, 0);
+ if (dismissKeyguard) {
+ mDismissing = true;
+ getKeyguardSecurityCallback().dismiss(true, userId);
+ }
+ } else {
+ if (isValidPassword) {
+ getKeyguardSecurityCallback().reportUnlockAttempt(userId, false, timeoutMs);
+ if (timeoutMs > 0) {
+ long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
+ userId, timeoutMs);
+ handleAttemptLockout(deadline);
+ }
+ }
+ if (timeoutMs == 0) {
+ mMessageAreaController.setMessage(mView.getWrongPasswordStringId());
+ }
+ }
+ mView.resetPasswordText(true /* animate */, !matched /* announce deletion if no match */);
+ }
+
+ protected void verifyPasswordAndUnlock() {
+ if (mDismissing) return; // already verified but haven't been dismissed; don't do it again.
+
+ final LockscreenCredential password = mView.getEnteredCredential();
+ mView.setPasswordEntryInputEnabled(false);
+ if (mPendingLockCheck != null) {
+ mPendingLockCheck.cancel(false);
+ }
+
+ final int userId = KeyguardUpdateMonitor.getCurrentUser();
+ if (password.size() <= MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT) {
+ // to avoid accidental lockout, only count attempts that are long enough to be a
+ // real password. This may require some tweaking.
+ mView.setPasswordEntryInputEnabled(true);
+ onPasswordChecked(userId, false /* matched */, 0, false /* not valid - too short */);
+ password.zeroize();
+ return;
+ }
+
+ mLatencyTracker.onActionStart(ACTION_CHECK_CREDENTIAL);
+ mLatencyTracker.onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED);
+
+ mKeyguardUpdateMonitor.setCredentialAttempted();
+ mPendingLockCheck = LockPatternChecker.checkCredential(
+ mLockPatternUtils,
+ password,
+ userId,
+ new LockPatternChecker.OnCheckCallback() {
+
+ @Override
+ public void onEarlyMatched() {
+ mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL);
+
+ onPasswordChecked(userId, true /* matched */, 0 /* timeoutMs */,
+ true /* isValidPassword */);
+ password.zeroize();
+ }
+
+ @Override
+ public void onChecked(boolean matched, int timeoutMs) {
+ mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL_UNLOCKED);
+ mView.setPasswordEntryInputEnabled(true);
+ mPendingLockCheck = null;
+ if (!matched) {
+ onPasswordChecked(userId, false /* matched */, timeoutMs,
+ true /* isValidPassword */);
+ }
+ password.zeroize();
+ }
+
+ @Override
+ public void onCancelled() {
+ // We already got dismissed with the early matched callback, so we cancelled
+ // the check. However, we still need to note down the latency.
+ mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL_UNLOCKED);
+ password.zeroize();
+ }
+ });
+ }
+
+ @Override
+ public void showPromptReason(int reason) {
+ if (reason != PROMPT_REASON_NONE) {
+ int promtReasonStringRes = mView.getPromptReasonStringRes(reason);
+ if (promtReasonStringRes != 0) {
+ mMessageAreaController.setMessage(promtReasonStringRes);
+ }
+ }
+ }
+
+ protected void onUserInput() {
+ getKeyguardSecurityCallback().userActivity();
+ getKeyguardSecurityCallback().onUserInput();
+ mMessageAreaController.setMessage("");
+ }
+
+ @Override
+ public void onResume(int reason) {
+ mResumed = true;
+ }
+
+ @Override
+ public void onPause() {
+ mResumed = false;
+
+ if (mCountdownTimer != null) {
+ mCountdownTimer.cancel();
+ mCountdownTimer = null;
+ }
+ if (mPendingLockCheck != null) {
+ mPendingLockCheck.cancel(false);
+ mPendingLockCheck = null;
+ }
+ reset();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index be21d203411e..36d5543f1c01 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -39,7 +39,6 @@ import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationBarView;
-import com.android.systemui.util.InjectionInflationController;
import javax.inject.Inject;
@@ -49,7 +48,6 @@ public class KeyguardDisplayManager {
private final MediaRouter mMediaRouter;
private final DisplayManager mDisplayService;
- private final InjectionInflationController mInjectableInflater;
private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
private final Context mContext;
@@ -92,10 +90,8 @@ public class KeyguardDisplayManager {
@Inject
public KeyguardDisplayManager(Context context,
- InjectionInflationController injectableInflater,
KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory) {
mContext = context;
- mInjectableInflater = injectableInflater;
mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
mMediaRouter = mContext.getSystemService(MediaRouter.class);
mDisplayService = mContext.getSystemService(DisplayManager.class);
@@ -131,8 +127,7 @@ public class KeyguardDisplayManager {
Presentation presentation = mPresentations.get(displayId);
if (presentation == null) {
final Presentation newPresentation = new KeyguardPresentation(mContext, display,
- mKeyguardStatusViewComponentFactory,
- mInjectableInflater.injectable(LayoutInflater.from(mContext)));
+ mKeyguardStatusViewComponentFactory, LayoutInflater.from(mContext));
newPresentation.setOnDismissListener(dialog -> {
if (newPresentation.equals(mPresentations.get(displayId))) {
mPresentations.remove(displayId);
@@ -250,7 +245,7 @@ public class KeyguardDisplayManager {
private static final int VIDEO_SAFE_REGION = 80; // Percentage of display width & height
private static final int MOVE_CLOCK_TIMEOUT = 10000; // 10s
private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
- private final LayoutInflater mInjectableLayoutInflater;
+ private final LayoutInflater mLayoutInflater;
private KeyguardClockSwitchController mKeyguardClockSwitchController;
private View mClock;
private int mUsableWidth;
@@ -270,10 +265,10 @@ public class KeyguardDisplayManager {
KeyguardPresentation(Context context, Display display,
KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
- LayoutInflater injectionLayoutInflater) {
+ LayoutInflater layoutInflater) {
super(context, display, R.style.Theme_SystemUI_KeyguardPresentation);
mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
- mInjectableLayoutInflater = injectionLayoutInflater;
+ mLayoutInflater = layoutInflater;
getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
setCancelable(false);
}
@@ -299,7 +294,7 @@ public class KeyguardDisplayManager {
mMarginLeft = (100 - VIDEO_SAFE_REGION) * p.x / 200;
mMarginTop = (100 - VIDEO_SAFE_REGION) * p.y / 200;
- setContentView(mInjectableLayoutInflater.inflate(R.layout.keyguard_presentation, null));
+ setContentView(mLayoutInflater.inflate(R.layout.keyguard_presentation, null));
// Logic to make the lock screen fullscreen
getWindow().getDecorView().setSystemUiVisibility(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
index 7aabb17de90c..9ffa658da0e8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
@@ -178,18 +178,18 @@ public class KeyguardHostViewController extends ViewController<KeyguardHostView>
/** Initialize the Controller. */
public void init() {
super.init();
- mView.setViewMediatorCallback(mViewMediatorCallback);
- // Update ViewMediator with the current input method requirements
- mViewMediatorCallback.setNeedsInput(mKeyguardSecurityContainerController.needsInput());
mKeyguardSecurityContainerController.init();
- mKeyguardSecurityContainerController.setSecurityCallback(mSecurityCallback);
- mKeyguardSecurityContainerController.showPrimarySecurityScreen(false);
}
@Override
protected void onViewAttached() {
+ mView.setViewMediatorCallback(mViewMediatorCallback);
+ // Update ViewMediator with the current input method requirements
+ mViewMediatorCallback.setNeedsInput(mKeyguardSecurityContainerController.needsInput());
mKeyguardUpdateMonitor.registerCallback(mUpdateCallback);
mView.setOnKeyListener(mOnKeyListener);
+ mKeyguardSecurityContainerController.setSecurityCallback(mSecurityCallback);
+ mKeyguardSecurityContainerController.showPrimarySecurityScreen(false);
}
@Override
@@ -350,7 +350,7 @@ public class KeyguardHostViewController extends ViewController<KeyguardHostView>
}
public boolean handleBackKey() {
- if (mKeyguardSecurityContainerController.getCurrentSecuritySelection()
+ if (mKeyguardSecurityContainerController.getCurrentSecurityMode()
!= SecurityMode.None) {
mKeyguardSecurityContainerController.dismiss(
false, KeyguardUpdateMonitor.getCurrentUser());
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
new file mode 100644
index 000000000000..d42a53cc875e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.keyguard;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.widget.LinearLayout;
+
+import androidx.annotation.Nullable;
+
+/**
+ * A Base class for all Keyguard password/pattern/pin related inputs.
+ */
+public abstract class KeyguardInputView extends LinearLayout {
+
+ public KeyguardInputView(Context context) {
+ super(context);
+ }
+
+ public KeyguardInputView(Context context,
+ @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public KeyguardInputView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ abstract CharSequence getTitle();
+
+ boolean disallowInterceptTouch(MotionEvent event) {
+ return false;
+ }
+
+ void startAppearAnimation() {}
+
+ boolean startDisappearAnimation(Runnable finishRunnable) {
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
new file mode 100644
index 000000000000..fbda818740e8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.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 com.android.keyguard;
+
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.telephony.TelephonyManager;
+import android.view.inputmethod.InputMethodManager;
+
+import com.android.internal.util.LatencyTracker;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.util.ViewController;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+
+import javax.inject.Inject;
+
+
+/** Controller for a {@link KeyguardSecurityView}. */
+public abstract class KeyguardInputViewController<T extends KeyguardInputView>
+ extends ViewController<T> implements KeyguardSecurityView {
+
+ private final SecurityMode mSecurityMode;
+ private final KeyguardSecurityCallback mKeyguardSecurityCallback;
+ private boolean mPaused;
+
+
+ // The following is used to ignore callbacks from SecurityViews that are no longer current
+ // (e.g. face unlock). This avoids unwanted asynchronous events from messing with the
+ // state for the current security method.
+ private KeyguardSecurityCallback mNullCallback = new KeyguardSecurityCallback() {
+ @Override
+ public void userActivity() { }
+ @Override
+ public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) { }
+ @Override
+ public boolean isVerifyUnlockOnly() {
+ return false;
+ }
+ @Override
+ public void dismiss(boolean securityVerified, int targetUserId) { }
+ @Override
+ public void dismiss(boolean authenticated, int targetId,
+ boolean bypassSecondaryLockScreen) { }
+ @Override
+ public void onUserInput() { }
+ @Override
+ public void reset() {}
+ };
+
+ protected KeyguardInputViewController(T view, SecurityMode securityMode,
+ KeyguardSecurityCallback keyguardSecurityCallback) {
+ super(view);
+ mSecurityMode = securityMode;
+ mKeyguardSecurityCallback = keyguardSecurityCallback;
+ }
+
+ @Override
+ protected void onViewAttached() {
+ }
+
+ @Override
+ protected void onViewDetached() {
+ }
+
+ SecurityMode getSecurityMode() {
+ return mSecurityMode;
+ }
+
+ protected KeyguardSecurityCallback getKeyguardSecurityCallback() {
+ if (mPaused) {
+ return mNullCallback;
+ }
+
+ return mKeyguardSecurityCallback;
+ }
+
+ @Override
+ public void reset() {
+ }
+
+ @Override
+ public void onPause() {
+ mPaused = true;
+ }
+
+ @Override
+ public void onResume(int reason) {
+ mPaused = false;
+ }
+
+ @Override
+ public void showPromptReason(int reason) {
+ }
+
+ @Override
+ public void showMessage(CharSequence message, ColorStateList colorState) {
+ }
+
+ public void startAppearAnimation() {
+ mView.startAppearAnimation();
+ }
+
+ public boolean startDisappearAnimation(Runnable finishRunnable) {
+ return mView.startDisappearAnimation(finishRunnable);
+ }
+
+ @Override
+ public CharSequence getTitle() {
+ return mView.getTitle();
+ }
+
+ /** Finds the index of this view in the suppplied parent view. */
+ public int getIndexIn(KeyguardSecurityViewFlipper view) {
+ return view.indexOfChild(mView);
+ }
+
+ /** Factory for a {@link KeyguardInputViewController}. */
+ public static class Factory {
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final LockPatternUtils mLockPatternUtils;
+ private final LatencyTracker mLatencyTracker;
+ private final KeyguardMessageAreaController.Factory mMessageAreaControllerFactory;
+ private final InputMethodManager mInputMethodManager;
+ private final DelayableExecutor mMainExecutor;
+ private final Resources mResources;
+ private LiftToActivateListener mLiftToActivateListener;
+ private TelephonyManager mTelephonyManager;
+
+ @Inject
+ public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor,
+ LockPatternUtils lockPatternUtils,
+ LatencyTracker latencyTracker,
+ KeyguardMessageAreaController.Factory messageAreaControllerFactory,
+ InputMethodManager inputMethodManager, @Main DelayableExecutor mainExecutor,
+ @Main Resources resources, LiftToActivateListener liftToActivateListener,
+ TelephonyManager telephonyManager) {
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mLockPatternUtils = lockPatternUtils;
+ mLatencyTracker = latencyTracker;
+ mMessageAreaControllerFactory = messageAreaControllerFactory;
+ mInputMethodManager = inputMethodManager;
+ mMainExecutor = mainExecutor;
+ mResources = resources;
+ mLiftToActivateListener = liftToActivateListener;
+ mTelephonyManager = telephonyManager;
+ }
+
+ /** Create a new {@link KeyguardInputViewController}. */
+ public KeyguardInputViewController create(KeyguardInputView keyguardInputView,
+ SecurityMode securityMode, KeyguardSecurityCallback keyguardSecurityCallback) {
+ if (keyguardInputView instanceof KeyguardPatternView) {
+ return new KeyguardPatternViewController((KeyguardPatternView) keyguardInputView,
+ mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
+ keyguardSecurityCallback, mLatencyTracker, mMessageAreaControllerFactory);
+ } else if (keyguardInputView instanceof KeyguardPasswordView) {
+ return new KeyguardPasswordViewController((KeyguardPasswordView) keyguardInputView,
+ mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
+ keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
+ mInputMethodManager, mMainExecutor, mResources);
+ } else if (keyguardInputView instanceof KeyguardPINView) {
+ return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
+ mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
+ keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
+ mLiftToActivateListener);
+ } else if (keyguardInputView instanceof KeyguardSimPinView) {
+ return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView,
+ mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
+ keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
+ mLiftToActivateListener, mTelephonyManager);
+ } else if (keyguardInputView instanceof KeyguardSimPukView) {
+ return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView,
+ mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
+ keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
+ mLiftToActivateListener, mTelephonyManager);
+ }
+
+ throw new RuntimeException("Unable to find controller for " + keyguardInputView);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
index a8b1451d92c7..1a0a4370fca4 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
@@ -16,8 +16,6 @@
package com.android.keyguard;
-import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
-
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
@@ -31,20 +29,14 @@ import android.util.TypedValue;
import android.view.View;
import android.widget.TextView;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.ConfigurationController;
import java.lang.ref.WeakReference;
-import javax.inject.Inject;
-import javax.inject.Named;
-
/***
* Manages a number of views inside of the given layout. See below for a list of widgets.
*/
-public class KeyguardMessageArea extends TextView implements SecurityMessageDisplay,
- ConfigurationController.ConfigurationListener {
+public class KeyguardMessageArea extends TextView implements SecurityMessageDisplay {
/** Handler token posted with accessibility announcement runnables. */
private static final Object ANNOUNCE_TOKEN = new Object();
@@ -56,71 +48,26 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp
private static final int DEFAULT_COLOR = -1;
private final Handler mHandler;
- private final ConfigurationController mConfigurationController;
private ColorStateList mDefaultColorState;
private CharSequence mMessage;
private ColorStateList mNextMessageColorState = ColorStateList.valueOf(DEFAULT_COLOR);
private boolean mBouncerVisible;
- private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
- public void onFinishedGoingToSleep(int why) {
- setSelected(false);
- }
-
- public void onStartedWakingUp() {
- setSelected(true);
- }
-
- @Override
- public void onKeyguardBouncerChanged(boolean bouncer) {
- mBouncerVisible = bouncer;
- update();
- }
- };
-
- public KeyguardMessageArea(Context context) {
- super(context, null);
- throw new IllegalStateException("This constructor should never be invoked");
- }
-
- @Inject
- public KeyguardMessageArea(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
- ConfigurationController configurationController) {
- this(context, attrs, Dependency.get(KeyguardUpdateMonitor.class), configurationController);
- }
-
- public KeyguardMessageArea(Context context, AttributeSet attrs, KeyguardUpdateMonitor monitor,
- ConfigurationController configurationController) {
+ public KeyguardMessageArea(Context context, AttributeSet attrs) {
super(context, attrs);
setLayerType(LAYER_TYPE_HARDWARE, null); // work around nested unclipped SaveLayer bug
- monitor.registerCallback(mInfoCallback);
mHandler = new Handler(Looper.myLooper());
- mConfigurationController = configurationController;
onThemeChanged();
}
@Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mConfigurationController.addCallback(this);
- onThemeChanged();
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mConfigurationController.removeCallback(this);
- }
-
- @Override
public void setNextMessageColor(ColorStateList colorState) {
mNextMessageColorState = colorState;
}
- @Override
- public void onThemeChanged() {
+ void onThemeChanged() {
TypedArray array = mContext.obtainStyledAttributes(new int[] {
R.attr.wallpaperTextColor
});
@@ -130,8 +77,7 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp
update();
}
- @Override
- public void onDensityOrFontScaleChanged() {
+ void onDensityOrFontScaleChanged() {
TypedArray array = mContext.obtainStyledAttributes(R.style.Keyguard_TextView, new int[] {
android.R.attr.textSize
});
@@ -177,12 +123,6 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp
return messageArea;
}
- @Override
- protected void onFinishInflate() {
- boolean shouldMarquee = Dependency.get(KeyguardUpdateMonitor.class).isDeviceInteractive();
- setSelected(shouldMarquee); // This is required to ensure marquee works
- }
-
private void securityMessageChanged(CharSequence message) {
mMessage = message;
update();
@@ -196,7 +136,7 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp
update();
}
- private void update() {
+ void update() {
CharSequence status = mMessage;
setVisibility(TextUtils.isEmpty(status) || !mBouncerVisible ? INVISIBLE : VISIBLE);
setText(status);
@@ -208,6 +148,9 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp
setTextColor(colorState);
}
+ public void setBouncerVisible(boolean bouncerVisible) {
+ mBouncerVisible = bouncerVisible;
+ }
/**
* Runnable used to delay accessibility announcements.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
index f056bdbb9706..1618e8e58055 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
@@ -16,7 +16,10 @@
package com.android.keyguard;
+import android.content.res.ColorStateList;
+
import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.util.ViewController;
import javax.inject.Inject;
@@ -26,6 +29,35 @@ public class KeyguardMessageAreaController extends ViewController<KeyguardMessag
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final ConfigurationController mConfigurationController;
+
+ private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
+ public void onFinishedGoingToSleep(int why) {
+ mView.setSelected(false);
+ }
+
+ public void onStartedWakingUp() {
+ mView.setSelected(true);
+ }
+
+ @Override
+ public void onKeyguardBouncerChanged(boolean bouncer) {
+ mView.setBouncerVisible(bouncer);
+ mView.update();
+ }
+ };
+
+ private ConfigurationListener mConfigurationListener = new ConfigurationListener() {
+ @Override
+ public void onThemeChanged() {
+ mView.onThemeChanged();
+ }
+
+ @Override
+ public void onDensityOrFontScaleChanged() {
+ mView.onDensityOrFontScaleChanged();
+ }
+ };
+
private KeyguardMessageAreaController(KeyguardMessageArea view,
KeyguardUpdateMonitor keyguardUpdateMonitor,
ConfigurationController configurationController) {
@@ -37,17 +69,31 @@ public class KeyguardMessageAreaController extends ViewController<KeyguardMessag
@Override
protected void onViewAttached() {
- //mConfigurationController.addCallback();
- //mKeyguardUpdateMonitor.registerCallback();
+ mConfigurationController.addCallback(mConfigurationListener);
+ mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
+ mView.setSelected(mKeyguardUpdateMonitor.isDeviceInteractive());
+ mView.onThemeChanged();
}
@Override
protected void onViewDetached() {
- //mConfigurationController.removeCallback();
- //mKeyguardUpdateMonitor.removeCallback();
+ mConfigurationController.removeCallback(mConfigurationListener);
+ mKeyguardUpdateMonitor.removeCallback(mInfoCallback);
+ }
+
+ public void setMessage(CharSequence s) {
+ mView.setMessage(s);
+ }
+
+ public void setMessage(int resId) {
+ mView.setMessage(resId);
+ }
+
+ public void setNextMessageColor(ColorStateList colorState) {
+ mView.setNextMessageColor(colorState);
}
- /** Factory for createing {@link com.android.keyguard.KeyguardMessageAreaController}. */
+ /** Factory for creating {@link com.android.keyguard.KeyguardMessageAreaController}. */
public static class Factory {
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final ConfigurationController mConfigurationController;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 12ea1d586e10..580d7043a220 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -24,7 +24,6 @@ import android.view.animation.AnimationUtils;
import com.android.settingslib.animation.AppearAnimationUtils;
import com.android.settingslib.animation.DisappearAnimationUtils;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
/**
@@ -40,10 +39,8 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
private ViewGroup mRow1;
private ViewGroup mRow2;
private ViewGroup mRow3;
- private View mDivider;
private int mDisappearYTranslation;
private View[][] mViews;
- private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
public KeyguardPINView(Context context) {
this(context, null);
@@ -63,15 +60,10 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
mContext, android.R.interpolator.fast_out_linear_in));
mDisappearYTranslation = getResources().getDimensionPixelSize(
R.dimen.disappear_y_translation);
- mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
}
@Override
protected void resetState() {
- super.resetState();
- if (mSecurityMessageDisplay != null) {
- mSecurityMessageDisplay.setMessage("");
- }
}
@Override
@@ -88,7 +80,6 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
mRow1 = findViewById(R.id.row1);
mRow2 = findViewById(R.id.row2);
mRow3 = findViewById(R.id.row3);
- mDivider = findViewById(R.id.divider);
mViews = new View[][]{
new View[]{
mRow0, null, null
@@ -112,18 +103,6 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
new View[]{
null, mEcaView, null
}};
-
- View cancelBtn = findViewById(R.id.cancel_button);
- if (cancelBtn != null) {
- cancelBtn.setOnClickListener(view -> {
- mCallback.reset();
- mCallback.onCancelClicked();
- });
- }
- }
-
- @Override
- public void showUsabilityHint() {
}
@Override
@@ -147,24 +126,21 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
});
}
- @Override
- public boolean startDisappearAnimation(final Runnable finishRunnable) {
+ public boolean startDisappearAnimation(boolean needsSlowUnlockTransition,
+ final Runnable finishRunnable) {
+
enableClipping(false);
setTranslationY(0);
AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 280 /* duration */,
mDisappearYTranslation, mDisappearAnimationUtils.getInterpolator());
- DisappearAnimationUtils disappearAnimationUtils = mKeyguardUpdateMonitor
- .needsSlowUnlockTransition()
+ DisappearAnimationUtils disappearAnimationUtils = needsSlowUnlockTransition
? mDisappearAnimationUtilsLocked
: mDisappearAnimationUtils;
disappearAnimationUtils.startAnimation2d(mViews,
- new Runnable() {
- @Override
- public void run() {
- enableClipping(true);
- if (finishRunnable != null) {
- finishRunnable.run();
- }
+ () -> {
+ enableClipping(true);
+ if (finishRunnable != null) {
+ finishRunnable.run();
}
});
return true;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 97317cf5580f..aaa5efec807e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -16,50 +16,37 @@
package com.android.keyguard;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
+
import android.content.Context;
import android.graphics.Rect;
-import android.os.UserHandle;
-import android.text.Editable;
-import android.text.InputType;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.text.method.TextKeyListener;
import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.view.View;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.InputMethodSubtype;
import android.widget.TextView;
-import android.widget.TextView.OnEditorActionListener;
import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.TextViewInputDisabler;
import com.android.systemui.R;
-
-import java.util.List;
/**
* Displays an alphanumeric (latin-1) key entry for the user to enter
* an unlock password
*/
-public class KeyguardPasswordView extends KeyguardAbsKeyInputView
- implements KeyguardSecurityView, OnEditorActionListener, TextWatcher {
+public class KeyguardPasswordView extends KeyguardAbsKeyInputView {
- private final boolean mShowImeAtScreenOn;
private final int mDisappearYTranslation;
// A delay constant to be used in a workaround for the situation where InputMethodManagerService
// is not switched to the new user yet.
// TODO: Remove this by ensuring such a race condition never happens.
- private static final int DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON = 500; // 500ms
- InputMethodManager mImm;
private TextView mPasswordEntry;
private TextViewInputDisabler mPasswordEntryDisabler;
- private View mSwitchImeButton;
private Interpolator mLinearOutSlowInInterpolator;
private Interpolator mFastOutLinearInInterpolator;
@@ -70,8 +57,6 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView
public KeyguardPasswordView(Context context, AttributeSet attrs) {
super(context, attrs);
- mShowImeAtScreenOn = context.getResources().
- getBoolean(R.bool.kg_show_ime_at_screen_on);
mDisappearYTranslation = getResources().getDimensionPixelSize(
R.dimen.disappear_y_translation);
mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
@@ -82,20 +67,6 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView
@Override
protected void resetState() {
- mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
- if (mSecurityMessageDisplay != null) {
- mSecurityMessageDisplay.setMessage("");
- }
- final boolean wasDisabled = mPasswordEntry.isEnabled();
- setPasswordEntryEnabled(true);
- setPasswordEntryInputEnabled(true);
- // Don't call showSoftInput when PasswordEntry is invisible or in pausing stage.
- if (!mResumed || !mPasswordEntry.isVisibleToUser()) {
- return;
- }
- if (wasDisabled) {
- mImm.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
- }
}
@Override
@@ -104,29 +75,6 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView
}
@Override
- public boolean needsInput() {
- return true;
- }
-
- @Override
- public void onResume(final int reason) {
- super.onResume(reason);
-
- // Wait a bit to focus the field so the focusable flag on the window is already set then.
- post(new Runnable() {
- @Override
- public void run() {
- if (isShown() && mPasswordEntry.isEnabled()) {
- mPasswordEntry.requestFocus();
- if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) {
- mImm.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
- }
- }
- }
- });
- }
-
- @Override
protected int getPromptReasonStringRes(int reason) {
switch (reason) {
case PROMPT_REASON_RESTART:
@@ -146,97 +94,13 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView
}
}
- @Override
- public void onPause() {
- super.onPause();
- mImm.hideSoftInputFromWindow(getWindowToken(), 0);
- }
-
- @Override
- public void onStartingToHide() {
- mImm.hideSoftInputFromWindow(getWindowToken(), 0);
- }
-
- private void updateSwitchImeButton() {
- // If there's more than one IME, enable the IME switcher button
- final boolean wasVisible = mSwitchImeButton.getVisibility() == View.VISIBLE;
- final boolean shouldBeVisible = hasMultipleEnabledIMEsOrSubtypes(mImm, false);
- if (wasVisible != shouldBeVisible) {
- mSwitchImeButton.setVisibility(shouldBeVisible ? View.VISIBLE : View.GONE);
- }
-
- // TODO: Check if we still need this hack.
- // If no icon is visible, reset the start margin on the password field so the text is
- // still centered.
- if (mSwitchImeButton.getVisibility() != View.VISIBLE) {
- android.view.ViewGroup.LayoutParams params = mPasswordEntry.getLayoutParams();
- if (params instanceof MarginLayoutParams) {
- final MarginLayoutParams mlp = (MarginLayoutParams) params;
- mlp.setMarginStart(0);
- mPasswordEntry.setLayoutParams(params);
- }
- }
- }
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mImm = (InputMethodManager) getContext().getSystemService(
- Context.INPUT_METHOD_SERVICE);
-
mPasswordEntry = findViewById(getPasswordTextViewId());
- mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
mPasswordEntryDisabler = new TextViewInputDisabler(mPasswordEntry);
- mPasswordEntry.setKeyListener(TextKeyListener.getInstance());
- mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT
- | InputType.TYPE_TEXT_VARIATION_PASSWORD);
- mPasswordEntry.setOnEditorActionListener(this);
- mPasswordEntry.addTextChangedListener(this);
-
- // Poke the wakelock any time the text is selected or modified
- mPasswordEntry.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- mCallback.userActivity();
- }
- });
-
- // Set selected property on so the view can send accessibility events.
- mPasswordEntry.setSelected(true);
-
- mSwitchImeButton = findViewById(R.id.switch_ime_button);
- mSwitchImeButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- mCallback.userActivity(); // Leave the screen on a bit longer
- // Do not show auxiliary subtypes in password lock screen.
- mImm.showInputMethodPickerFromSystem(false /* showAuxiliarySubtypes */,
- getContext().getDisplayId());
- }
- });
-
- View cancelBtn = findViewById(R.id.cancel_button);
- if (cancelBtn != null) {
- cancelBtn.setOnClickListener(view -> {
- mCallback.reset();
- mCallback.onCancelClicked();
- });
- }
-
- // If there's more than one IME, enable the IME switcher button
- updateSwitchImeButton();
-
- // When we the current user is switching, InputMethodManagerService sometimes has not
- // switched internal state yet here. As a quick workaround, we check the keyboard state
- // again.
- // TODO: Remove this workaround by ensuring such a race condition never happens.
- postDelayed(new Runnable() {
- @Override
- public void run() {
- updateSwitchImeButton();
- }
- }, DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON);
}
@Override
@@ -265,59 +129,6 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView
mPasswordEntryDisabler.setInputEnabled(enabled);
}
- /**
- * Method adapted from com.android.inputmethod.latin.Utils
- *
- * @param imm The input method manager
- * @param shouldIncludeAuxiliarySubtypes
- * @return true if we have multiple IMEs to choose from
- */
- private boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm,
- final boolean shouldIncludeAuxiliarySubtypes) {
- final List<InputMethodInfo> enabledImis =
- imm.getEnabledInputMethodListAsUser(KeyguardUpdateMonitor.getCurrentUser());
-
- // Number of the filtered IMEs
- int filteredImisCount = 0;
-
- for (InputMethodInfo imi : enabledImis) {
- // We can return true immediately after we find two or more filtered IMEs.
- if (filteredImisCount > 1) return true;
- final List<InputMethodSubtype> subtypes =
- imm.getEnabledInputMethodSubtypeList(imi, true);
- // IMEs that have no subtypes should be counted.
- if (subtypes.isEmpty()) {
- ++filteredImisCount;
- continue;
- }
-
- int auxCount = 0;
- for (InputMethodSubtype subtype : subtypes) {
- if (subtype.isAuxiliary()) {
- ++auxCount;
- }
- }
- final int nonAuxCount = subtypes.size() - auxCount;
-
- // IMEs that have one or more non-auxiliary subtypes should be counted.
- // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary
- // subtypes should be counted as well.
- if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
- ++filteredImisCount;
- continue;
- }
- }
-
- return filteredImisCount > 1
- // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's enabled
- // input method subtype (The current IME should be LatinIME.)
- || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;
- }
-
- @Override
- public void showUsabilityHint() {
- }
-
@Override
public int getWrongPasswordStringId() {
return R.string.kg_wrong_password;
@@ -346,45 +157,8 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView
}
@Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- if (mCallback != null) {
- mCallback.userActivity();
- }
- }
-
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- }
-
- @Override
- public void afterTextChanged(Editable s) {
- // Poor man's user edit detection, assuming empty text is programmatic and everything else
- // is from the user.
- if (!TextUtils.isEmpty(s)) {
- onUserInput();
- }
- }
-
- @Override
- public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
- // Check if this was the result of hitting the enter key
- final boolean isSoftImeEvent = event == null
- && (actionId == EditorInfo.IME_NULL
- || actionId == EditorInfo.IME_ACTION_DONE
- || actionId == EditorInfo.IME_ACTION_NEXT);
- final boolean isKeyboardEnterKey = event != null
- && KeyEvent.isConfirmKey(event.getKeyCode())
- && event.getAction() == KeyEvent.ACTION_DOWN;
- if (isSoftImeEvent || isKeyboardEnterKey) {
- verifyPasswordAndUnlock();
- return true;
- }
- return false;
- }
-
- @Override
public CharSequence getTitle() {
- return getContext().getString(
+ return getResources().getString(
com.android.internal.R.string.keyguard_accessibility_password_unlock);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
new file mode 100644
index 000000000000..d34ea8c5e018
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.keyguard;
+
+import android.content.res.Resources;
+import android.os.UserHandle;
+import android.text.Editable;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.text.method.TextKeyListener;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+import com.android.internal.util.LatencyTracker;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+
+import java.util.List;
+
+public class KeyguardPasswordViewController
+ extends KeyguardAbsKeyInputViewController<KeyguardPasswordView> {
+
+ private static final int DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON = 500; // 500ms
+
+ private final KeyguardSecurityCallback mKeyguardSecurityCallback;
+ private final InputMethodManager mInputMethodManager;
+ private final DelayableExecutor mMainExecutor;
+ private final boolean mShowImeAtScreenOn;
+ private TextView mPasswordEntry;
+ private View mSwitchImeButton;
+
+ private final OnEditorActionListener mOnEditorActionListener = (v, actionId, event) -> {
+ // Check if this was the result of hitting the enter key
+ final boolean isSoftImeEvent = event == null
+ && (actionId == EditorInfo.IME_NULL
+ || actionId == EditorInfo.IME_ACTION_DONE
+ || actionId == EditorInfo.IME_ACTION_NEXT);
+ final boolean isKeyboardEnterKey = event != null
+ && KeyEvent.isConfirmKey(event.getKeyCode())
+ && event.getAction() == KeyEvent.ACTION_DOWN;
+ if (isSoftImeEvent || isKeyboardEnterKey) {
+ verifyPasswordAndUnlock();
+ return true;
+ }
+ return false;
+ };
+
+ private final TextWatcher mTextWatcher = new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ mKeyguardSecurityCallback.userActivity();
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ if (!TextUtils.isEmpty(s)) {
+ onUserInput();
+ }
+ }
+ };
+
+ protected KeyguardPasswordViewController(KeyguardPasswordView view,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ SecurityMode securityMode,
+ LockPatternUtils lockPatternUtils,
+ KeyguardSecurityCallback keyguardSecurityCallback,
+ KeyguardMessageAreaController.Factory messageAreaControllerFactory,
+ LatencyTracker latencyTracker,
+ InputMethodManager inputMethodManager,
+ @Main DelayableExecutor mainExecutor,
+ @Main Resources resources) {
+ super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
+ messageAreaControllerFactory, latencyTracker);
+ mKeyguardSecurityCallback = keyguardSecurityCallback;
+ mInputMethodManager = inputMethodManager;
+ mMainExecutor = mainExecutor;
+ mShowImeAtScreenOn = resources.getBoolean(R.bool.kg_show_ime_at_screen_on);
+ mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
+ mSwitchImeButton = mView.findViewById(R.id.switch_ime_button);
+ }
+
+ @Override
+ protected void onViewAttached() {
+ super.onViewAttached();
+ mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
+ mPasswordEntry.setKeyListener(TextKeyListener.getInstance());
+ mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT
+ | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+
+ // Set selected property on so the view can send accessibility events.
+ mPasswordEntry.setSelected(true);
+ mPasswordEntry.setOnEditorActionListener(mOnEditorActionListener);
+ mPasswordEntry.addTextChangedListener(mTextWatcher);
+ // Poke the wakelock any time the text is selected or modified
+ mPasswordEntry.setOnClickListener(v -> mKeyguardSecurityCallback.userActivity());
+
+ mSwitchImeButton.setOnClickListener(v -> {
+ mKeyguardSecurityCallback.userActivity(); // Leave the screen on a bit longer
+ // Do not show auxiliary subtypes in password lock screen.
+ mInputMethodManager.showInputMethodPickerFromSystem(false,
+ mView.getContext().getDisplayId());
+ });
+
+ View cancelBtn = mView.findViewById(R.id.cancel_button);
+ if (cancelBtn != null) {
+ cancelBtn.setOnClickListener(view -> {
+ mKeyguardSecurityCallback.reset();
+ mKeyguardSecurityCallback.onCancelClicked();
+ });
+ }
+
+ // If there's more than one IME, enable the IME switcher button
+ updateSwitchImeButton();
+
+ // When we the current user is switching, InputMethodManagerService sometimes has not
+ // switched internal state yet here. As a quick workaround, we check the keyboard state
+ // again.
+ // TODO: Remove this workaround by ensuring such a race condition never happens.
+ mMainExecutor.executeDelayed(
+ this::updateSwitchImeButton, DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON);
+ }
+
+ @Override
+ protected void onViewDetached() {
+ super.onViewDetached();
+ mPasswordEntry.setOnEditorActionListener(null);
+ }
+
+ @Override
+ public boolean needsInput() {
+ return true;
+ }
+
+ @Override
+ void resetState() {
+ mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
+ mMessageAreaController.setMessage("");
+ final boolean wasDisabled = mPasswordEntry.isEnabled();
+ mView.setPasswordEntryEnabled(true);
+ mView.setPasswordEntryInputEnabled(true);
+ // Don't call showSoftInput when PasswordEntry is invisible or in pausing stage.
+ if (!mResumed || !mPasswordEntry.isVisibleToUser()) {
+ return;
+ }
+ if (wasDisabled) {
+ mInputMethodManager.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
+ }
+ }
+
+ @Override
+ public void onResume(int reason) {
+ super.onResume(reason);
+ // Wait a bit to focus the field so the focusable flag on the window is already set then.
+ mMainExecutor.execute(() -> {
+ if (mView.isShown() && mPasswordEntry.isEnabled()) {
+ mPasswordEntry.requestFocus();
+ if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) {
+ mInputMethodManager.showSoftInput(
+ mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mInputMethodManager.hideSoftInputFromWindow(mView.getWindowToken(), 0);
+ }
+
+ @Override
+ public void onStartingToHide() {
+ mInputMethodManager.hideSoftInputFromWindow(mView.getWindowToken(), 0);
+ }
+
+ private void updateSwitchImeButton() {
+ // If there's more than one IME, enable the IME switcher button
+ final boolean wasVisible = mSwitchImeButton.getVisibility() == View.VISIBLE;
+ final boolean shouldBeVisible = hasMultipleEnabledIMEsOrSubtypes(
+ mInputMethodManager, false);
+ if (wasVisible != shouldBeVisible) {
+ mSwitchImeButton.setVisibility(shouldBeVisible ? View.VISIBLE : View.GONE);
+ }
+
+ // TODO: Check if we still need this hack.
+ // If no icon is visible, reset the start margin on the password field so the text is
+ // still centered.
+ if (mSwitchImeButton.getVisibility() != View.VISIBLE) {
+ android.view.ViewGroup.LayoutParams params = mPasswordEntry.getLayoutParams();
+ if (params instanceof MarginLayoutParams) {
+ final MarginLayoutParams mlp = (MarginLayoutParams) params;
+ mlp.setMarginStart(0);
+ mPasswordEntry.setLayoutParams(params);
+ }
+ }
+ }
+
+ /**
+ * Method adapted from com.android.inputmethod.latin.Utils
+ *
+ * @param imm The input method manager
+ * @param shouldIncludeAuxiliarySubtypes
+ * @return true if we have multiple IMEs to choose from
+ */
+ private boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm,
+ final boolean shouldIncludeAuxiliarySubtypes) {
+ final List<InputMethodInfo> enabledImis =
+ imm.getEnabledInputMethodListAsUser(KeyguardUpdateMonitor.getCurrentUser());
+
+ // Number of the filtered IMEs
+ int filteredImisCount = 0;
+
+ for (InputMethodInfo imi : enabledImis) {
+ // We can return true immediately after we find two or more filtered IMEs.
+ if (filteredImisCount > 1) return true;
+ final List<InputMethodSubtype> subtypes =
+ imm.getEnabledInputMethodSubtypeList(imi, true);
+ // IMEs that have no subtypes should be counted.
+ if (subtypes.isEmpty()) {
+ ++filteredImisCount;
+ continue;
+ }
+
+ int auxCount = 0;
+ for (InputMethodSubtype subtype : subtypes) {
+ if (subtype.isAuxiliary()) {
+ ++auxCount;
+ }
+ }
+ final int nonAuxCount = subtypes.size() - auxCount;
+
+ // IMEs that have one or more non-auxiliary subtypes should be counted.
+ // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary
+ // subtypes should be counted as well.
+ if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
+ ++filteredImisCount;
+ continue;
+ }
+ }
+
+ return filteredImisCount > 1
+ // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's
+ //enabled input method subtype (The current IME should be LatinIME.)
+ || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
index c4a9fcb45284..bdcf467c2456 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -15,62 +15,39 @@
*/
package com.android.keyguard;
-import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL;
-import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED;
-
import android.content.Context;
-import android.content.res.ColorStateList;
import android.graphics.Rect;
-import android.os.AsyncTask;
-import android.os.CountDownTimer;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
-import android.widget.LinearLayout;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.LatencyTracker;
-import com.android.internal.widget.LockPatternChecker;
-import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternView;
-import com.android.internal.widget.LockscreenCredential;
import com.android.settingslib.animation.AppearAnimationCreator;
import com.android.settingslib.animation.AppearAnimationUtils;
import com.android.settingslib.animation.DisappearAnimationUtils;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
-import java.util.List;
-
-public class KeyguardPatternView extends LinearLayout implements KeyguardSecurityView,
- AppearAnimationCreator<LockPatternView.CellState>,
- EmergencyButton.EmergencyButtonCallback {
+public class KeyguardPatternView extends KeyguardInputView
+ implements AppearAnimationCreator<LockPatternView.CellState> {
private static final String TAG = "SecurityPatternView";
private static final boolean DEBUG = KeyguardConstants.DEBUG;
- // how long before we clear the wrong pattern
- private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000;
// how long we stay awake after each key beyond MIN_PATTERN_BEFORE_POKE_WAKELOCK
private static final int UNLOCK_PATTERN_WAKE_INTERVAL_MS = 7000;
- // how many cells the user has to cross before we poke the wakelock
- private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2;
-
// How much we scale up the duration of the disappear animation when the current user is locked
public static final float DISAPPEAR_MULTIPLIER_LOCKED = 1.5f;
// Extra padding, in pixels, that should eat touch events.
private static final int PATTERNS_TOUCH_AREA_EXTENSION = 40;
- private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final AppearAnimationUtils mAppearAnimationUtils;
private final DisappearAnimationUtils mDisappearAnimationUtils;
private final DisappearAnimationUtils mDisappearAnimationUtilsLocked;
@@ -78,11 +55,7 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit
private final Rect mTempRect = new Rect();
private final Rect mLockPatternScreenBounds = new Rect();
- private CountDownTimer mCountdownTimer = null;
- private LockPatternUtils mLockPatternUtils;
- private AsyncTask<?, ?, ?> mPendingLockCheck;
private LockPatternView mLockPatternView;
- private KeyguardSecurityCallback mCallback;
/**
* Keeps track of the last time we poked the wake lock during dispatching of the touch event.
@@ -92,26 +65,9 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit
*/
private long mLastPokeTime = -UNLOCK_PATTERN_WAKE_INTERVAL_MS;
- /**
- * Useful for clearing out the wrong pattern after a delay
- */
- private Runnable mCancelPatternRunnable = new Runnable() {
- @Override
- public void run() {
- mLockPatternView.clearPattern();
- }
- };
- @VisibleForTesting
KeyguardMessageArea mSecurityMessageDisplay;
private View mEcaView;
private ViewGroup mContainer;
- private int mDisappearYTranslation;
-
- enum FooterMode {
- Normal,
- ForgotLockPattern,
- VerifyUnlocked
- }
public KeyguardPatternView(Context context) {
this(context, null);
@@ -119,7 +75,6 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit
public KeyguardPatternView(Context context, AttributeSet attrs) {
super(context, attrs);
- mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
mAppearAnimationUtils = new AppearAnimationUtils(context,
AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 1.5f /* translationScale */,
2.0f /* delayScale */, AnimationUtils.loadInterpolator(
@@ -132,50 +87,16 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit
(long) (125 * DISAPPEAR_MULTIPLIER_LOCKED), 1.2f /* translationScale */,
0.6f /* delayScale */, AnimationUtils.loadInterpolator(
mContext, android.R.interpolator.fast_out_linear_in));
- mDisappearYTranslation = getResources().getDimensionPixelSize(
- R.dimen.disappear_y_translation);
- }
-
- @Override
- public void setKeyguardCallback(KeyguardSecurityCallback callback) {
- mCallback = callback;
- }
-
- @Override
- public void setLockPatternUtils(LockPatternUtils utils) {
- mLockPatternUtils = utils;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mLockPatternUtils = mLockPatternUtils == null
- ? new LockPatternUtils(mContext) : mLockPatternUtils;
mLockPatternView = findViewById(R.id.lockPatternView);
- mLockPatternView.setSaveEnabled(false);
- mLockPatternView.setOnPatternListener(new UnlockPatternListener());
- mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
- KeyguardUpdateMonitor.getCurrentUser()));
-
- // vibrate mode will be the same for the life of this screen
- mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
mEcaView = findViewById(R.id.keyguard_selector_fade_container);
mContainer = findViewById(R.id.container);
-
- EmergencyButton button = findViewById(R.id.emergency_call_button);
- if (button != null) {
- button.setCallback(this);
- }
-
- View cancelBtn = findViewById(R.id.cancel_button);
- if (cancelBtn != null) {
- cancelBtn.setOnClickListener(view -> {
- mCallback.reset();
- mCallback.onCancelClicked();
- });
- }
}
@Override
@@ -185,11 +106,6 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit
}
@Override
- public void onEmergencyButtonClickedWhenInCall() {
- mCallback.reset();
- }
-
- @Override
public boolean onTouchEvent(MotionEvent ev) {
boolean result = super.onTouchEvent(ev);
// as long as the user is entering a pattern (i.e sending a touch event that was handled
@@ -217,248 +133,11 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit
}
@Override
- public void reset() {
- // reset lock pattern
- mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
- KeyguardUpdateMonitor.getCurrentUser()));
- mLockPatternView.enableInput();
- mLockPatternView.setEnabled(true);
- mLockPatternView.clearPattern();
-
- if (mSecurityMessageDisplay == null) {
- return;
- }
-
- // if the user is currently locked out, enforce it.
- long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
- KeyguardUpdateMonitor.getCurrentUser());
- if (deadline != 0) {
- handleAttemptLockout(deadline);
- } else {
- displayDefaultSecurityMessage();
- }
- }
-
- private void displayDefaultSecurityMessage() {
- if (mSecurityMessageDisplay != null) {
- mSecurityMessageDisplay.setMessage("");
- }
- }
-
- @Override
- public void showUsabilityHint() {
- }
-
- @Override
- public boolean disallowInterceptTouch(MotionEvent event) {
+ boolean disallowInterceptTouch(MotionEvent event) {
return !mLockPatternView.isEmpty()
|| mLockPatternScreenBounds.contains((int) event.getRawX(), (int) event.getRawY());
}
- /** TODO: hook this up */
- public void cleanUp() {
- if (DEBUG) Log.v(TAG, "Cleanup() called on " + this);
- mLockPatternUtils = null;
- mLockPatternView.setOnPatternListener(null);
- }
-
- private class UnlockPatternListener implements LockPatternView.OnPatternListener {
-
- @Override
- public void onPatternStart() {
- mLockPatternView.removeCallbacks(mCancelPatternRunnable);
- mSecurityMessageDisplay.setMessage("");
- }
-
- @Override
- public void onPatternCleared() {
- }
-
- @Override
- public void onPatternCellAdded(List<LockPatternView.Cell> pattern) {
- mCallback.userActivity();
- mCallback.onUserInput();
- }
-
- @Override
- public void onPatternDetected(final List<LockPatternView.Cell> pattern) {
- mKeyguardUpdateMonitor.setCredentialAttempted();
- mLockPatternView.disableInput();
- if (mPendingLockCheck != null) {
- mPendingLockCheck.cancel(false);
- }
-
- final int userId = KeyguardUpdateMonitor.getCurrentUser();
- if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
- mLockPatternView.enableInput();
- onPatternChecked(userId, false, 0, false /* not valid - too short */);
- return;
- }
-
- if (LatencyTracker.isEnabled(mContext)) {
- LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL);
- LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED);
- }
- mPendingLockCheck = LockPatternChecker.checkCredential(
- mLockPatternUtils,
- LockscreenCredential.createPattern(pattern),
- userId,
- new LockPatternChecker.OnCheckCallback() {
-
- @Override
- public void onEarlyMatched() {
- if (LatencyTracker.isEnabled(mContext)) {
- LatencyTracker.getInstance(mContext).onActionEnd(
- ACTION_CHECK_CREDENTIAL);
- }
- onPatternChecked(userId, true /* matched */, 0 /* timeoutMs */,
- true /* isValidPattern */);
- }
-
- @Override
- public void onChecked(boolean matched, int timeoutMs) {
- if (LatencyTracker.isEnabled(mContext)) {
- LatencyTracker.getInstance(mContext).onActionEnd(
- ACTION_CHECK_CREDENTIAL_UNLOCKED);
- }
- mLockPatternView.enableInput();
- mPendingLockCheck = null;
- if (!matched) {
- onPatternChecked(userId, false /* matched */, timeoutMs,
- true /* isValidPattern */);
- }
- }
-
- @Override
- public void onCancelled() {
- // We already got dismissed with the early matched callback, so we
- // cancelled the check. However, we still need to note down the latency.
- if (LatencyTracker.isEnabled(mContext)) {
- LatencyTracker.getInstance(mContext).onActionEnd(
- ACTION_CHECK_CREDENTIAL_UNLOCKED);
- }
- }
- });
- if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
- mCallback.userActivity();
- mCallback.onUserInput();
- }
- }
-
- private void onPatternChecked(int userId, boolean matched, int timeoutMs,
- boolean isValidPattern) {
- boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId;
- if (matched) {
- mCallback.reportUnlockAttempt(userId, true, 0);
- if (dismissKeyguard) {
- mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct);
- mCallback.dismiss(true, userId);
- }
- } else {
- mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
- if (isValidPattern) {
- mCallback.reportUnlockAttempt(userId, false, timeoutMs);
- if (timeoutMs > 0) {
- long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
- userId, timeoutMs);
- handleAttemptLockout(deadline);
- }
- }
- if (timeoutMs == 0) {
- mSecurityMessageDisplay.setMessage(R.string.kg_wrong_pattern);
- mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS);
- }
- }
- }
- }
-
- private void handleAttemptLockout(long elapsedRealtimeDeadline) {
- mLockPatternView.clearPattern();
- mLockPatternView.setEnabled(false);
- final long elapsedRealtime = SystemClock.elapsedRealtime();
- final long secondsInFuture = (long) Math.ceil(
- (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0);
- mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) {
-
- @Override
- public void onTick(long millisUntilFinished) {
- final int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0);
- mSecurityMessageDisplay.setMessage(mContext.getResources().getQuantityString(
- R.plurals.kg_too_many_failed_attempts_countdown,
- secondsRemaining, secondsRemaining));
- }
-
- @Override
- public void onFinish() {
- mLockPatternView.setEnabled(true);
- displayDefaultSecurityMessage();
- }
-
- }.start();
- }
-
- @Override
- public boolean needsInput() {
- return false;
- }
-
- @Override
- public void onPause() {
- if (mCountdownTimer != null) {
- mCountdownTimer.cancel();
- mCountdownTimer = null;
- }
- if (mPendingLockCheck != null) {
- mPendingLockCheck.cancel(false);
- mPendingLockCheck = null;
- }
- displayDefaultSecurityMessage();
- }
-
- @Override
- public void onResume(int reason) {
- }
-
- @Override
- public KeyguardSecurityCallback getCallback() {
- return mCallback;
- }
-
- @Override
- public void showPromptReason(int reason) {
- switch (reason) {
- case PROMPT_REASON_RESTART:
- mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_restart_pattern);
- break;
- case PROMPT_REASON_TIMEOUT:
- mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_timeout_pattern);
- break;
- case PROMPT_REASON_DEVICE_ADMIN:
- mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_device_admin);
- break;
- case PROMPT_REASON_USER_REQUEST:
- mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_user_request);
- break;
- case PROMPT_REASON_PREPARE_FOR_UPDATE:
- mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_timeout_pattern);
- break;
- case PROMPT_REASON_NONE:
- break;
- default:
- mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_timeout_pattern);
- break;
- }
- }
-
- @Override
- public void showMessage(CharSequence message, ColorStateList colorState) {
- if (colorState != null) {
- mSecurityMessageDisplay.setNextMessageColor(colorState);
- }
- mSecurityMessageDisplay.setMessage(message);
- }
-
- @Override
public void startAppearAnimation() {
enableClipping(false);
setAlpha(1f);
@@ -467,12 +146,7 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit
0, mAppearAnimationUtils.getInterpolator());
mAppearAnimationUtils.startAnimation2d(
mLockPatternView.getCellStates(),
- new Runnable() {
- @Override
- public void run() {
- enableClipping(true);
- }
- },
+ () -> enableClipping(true),
this);
if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) {
mAppearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0,
@@ -484,11 +158,9 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit
}
}
- @Override
- public boolean startDisappearAnimation(final Runnable finishRunnable) {
- float durationMultiplier = mKeyguardUpdateMonitor.needsSlowUnlockTransition()
- ? DISAPPEAR_MULTIPLIER_LOCKED
- : 1f;
+ public boolean startDisappearAnimation(boolean needsSlowUnlockTransition,
+ final Runnable finishRunnable) {
+ float durationMultiplier = needsSlowUnlockTransition ? DISAPPEAR_MULTIPLIER_LOCKED : 1f;
mLockPatternView.clearPattern();
enableClipping(false);
setTranslationY(0);
@@ -497,10 +169,8 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit
-mDisappearAnimationUtils.getStartTranslation(),
mDisappearAnimationUtils.getInterpolator());
- DisappearAnimationUtils disappearAnimationUtils = mKeyguardUpdateMonitor
- .needsSlowUnlockTransition()
- ? mDisappearAnimationUtilsLocked
- : mDisappearAnimationUtils;
+ DisappearAnimationUtils disappearAnimationUtils = needsSlowUnlockTransition
+ ? mDisappearAnimationUtilsLocked : mDisappearAnimationUtils;
disappearAnimationUtils.startAnimation2d(mLockPatternView.getCellStates(),
() -> {
enableClipping(true);
@@ -549,7 +219,7 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit
@Override
public CharSequence getTitle() {
- return getContext().getString(
+ return getResources().getString(
com.android.internal.R.string.keyguard_accessibility_pattern_unlock);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
new file mode 100644
index 000000000000..3db9db7be00c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.keyguard;
+
+import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL;
+import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED;
+
+import android.content.res.ColorStateList;
+import android.os.AsyncTask;
+import android.os.CountDownTimer;
+import android.os.SystemClock;
+import android.view.View;
+
+import com.android.internal.util.LatencyTracker;
+import com.android.internal.widget.LockPatternChecker;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternView;
+import com.android.internal.widget.LockPatternView.Cell;
+import com.android.internal.widget.LockscreenCredential;
+import com.android.keyguard.EmergencyButton.EmergencyButtonCallback;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.R;
+
+import java.util.List;
+
+public class KeyguardPatternViewController
+ extends KeyguardInputViewController<KeyguardPatternView> {
+
+ // how many cells the user has to cross before we poke the wakelock
+ private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2;
+
+ // how long before we clear the wrong pattern
+ private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000;
+
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final LockPatternUtils mLockPatternUtils;
+ private final LatencyTracker mLatencyTracker;
+ private final KeyguardMessageAreaController.Factory mMessageAreaControllerFactory;
+
+ private KeyguardMessageAreaController mMessageAreaController;
+ private LockPatternView mLockPatternView;
+ private CountDownTimer mCountdownTimer;
+ private AsyncTask<?, ?, ?> mPendingLockCheck;
+
+ private EmergencyButtonCallback mEmergencyButtonCallback = new EmergencyButtonCallback() {
+ @Override
+ public void onEmergencyButtonClickedWhenInCall() {
+ getKeyguardSecurityCallback().reset();
+ }
+ };
+
+ /**
+ * Useful for clearing out the wrong pattern after a delay
+ */
+ private Runnable mCancelPatternRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mLockPatternView.clearPattern();
+ }
+ };
+
+ private class UnlockPatternListener implements LockPatternView.OnPatternListener {
+
+ @Override
+ public void onPatternStart() {
+ mLockPatternView.removeCallbacks(mCancelPatternRunnable);
+ mMessageAreaController.setMessage("");
+ }
+
+ @Override
+ public void onPatternCleared() {
+ }
+
+ @Override
+ public void onPatternCellAdded(List<Cell> pattern) {
+ getKeyguardSecurityCallback().userActivity();
+ getKeyguardSecurityCallback().onUserInput();
+ }
+
+ @Override
+ public void onPatternDetected(final List<LockPatternView.Cell> pattern) {
+ mKeyguardUpdateMonitor.setCredentialAttempted();
+ mLockPatternView.disableInput();
+ if (mPendingLockCheck != null) {
+ mPendingLockCheck.cancel(false);
+ }
+
+ final int userId = KeyguardUpdateMonitor.getCurrentUser();
+ if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
+ mLockPatternView.enableInput();
+ onPatternChecked(userId, false, 0, false /* not valid - too short */);
+ return;
+ }
+
+ mLatencyTracker.onActionStart(ACTION_CHECK_CREDENTIAL);
+ mLatencyTracker.onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED);
+ mPendingLockCheck = LockPatternChecker.checkCredential(
+ mLockPatternUtils,
+ LockscreenCredential.createPattern(pattern),
+ userId,
+ new LockPatternChecker.OnCheckCallback() {
+
+ @Override
+ public void onEarlyMatched() {
+ mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL);
+ onPatternChecked(userId, true /* matched */, 0 /* timeoutMs */,
+ true /* isValidPattern */);
+ }
+
+ @Override
+ public void onChecked(boolean matched, int timeoutMs) {
+ mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL_UNLOCKED);
+ mLockPatternView.enableInput();
+ mPendingLockCheck = null;
+ if (!matched) {
+ onPatternChecked(userId, false /* matched */, timeoutMs,
+ true /* isValidPattern */);
+ }
+ }
+
+ @Override
+ public void onCancelled() {
+ // We already got dismissed with the early matched callback, so we
+ // cancelled the check. However, we still need to note down the latency.
+ mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL_UNLOCKED);
+ }
+ });
+ if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
+ getKeyguardSecurityCallback().userActivity();
+ getKeyguardSecurityCallback().onUserInput();
+ }
+ }
+
+ private void onPatternChecked(int userId, boolean matched, int timeoutMs,
+ boolean isValidPattern) {
+ boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId;
+ if (matched) {
+ getKeyguardSecurityCallback().reportUnlockAttempt(userId, true, 0);
+ if (dismissKeyguard) {
+ mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct);
+ getKeyguardSecurityCallback().dismiss(true, userId);
+ }
+ } else {
+ mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
+ if (isValidPattern) {
+ getKeyguardSecurityCallback().reportUnlockAttempt(userId, false, timeoutMs);
+ if (timeoutMs > 0) {
+ long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
+ userId, timeoutMs);
+ handleAttemptLockout(deadline);
+ }
+ }
+ if (timeoutMs == 0) {
+ mMessageAreaController.setMessage(R.string.kg_wrong_pattern);
+ mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS);
+ }
+ }
+ }
+ }
+
+ protected KeyguardPatternViewController(KeyguardPatternView view,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ SecurityMode securityMode,
+ LockPatternUtils lockPatternUtils,
+ KeyguardSecurityCallback keyguardSecurityCallback,
+ LatencyTracker latencyTracker,
+ KeyguardMessageAreaController.Factory messageAreaControllerFactory) {
+ super(view, securityMode, keyguardSecurityCallback);
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mLockPatternUtils = lockPatternUtils;
+ mLatencyTracker = latencyTracker;
+ mMessageAreaControllerFactory = messageAreaControllerFactory;
+ KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView);
+ mMessageAreaController = mMessageAreaControllerFactory.create(kma);
+ mLockPatternView = mView.findViewById(R.id.lockPatternView);
+ }
+
+ @Override
+ public void init() {
+ super.init();
+ mMessageAreaController.init();
+ }
+
+ @Override
+ protected void onViewAttached() {
+ mLockPatternView.setOnPatternListener(new UnlockPatternListener());
+ mLockPatternView.setSaveEnabled(false);
+ mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
+ KeyguardUpdateMonitor.getCurrentUser()));
+ // vibrate mode will be the same for the life of this screen
+ mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
+
+ EmergencyButton button = mView.findViewById(R.id.emergency_call_button);
+ if (button != null) {
+ button.setCallback(mEmergencyButtonCallback);
+ }
+
+ View cancelBtn = mView.findViewById(R.id.cancel_button);
+ if (cancelBtn != null) {
+ cancelBtn.setOnClickListener(view -> {
+ getKeyguardSecurityCallback().reset();
+ getKeyguardSecurityCallback().onCancelClicked();
+ });
+ }
+ }
+
+ @Override
+ protected void onViewDetached() {
+ super.onViewDetached();
+ mLockPatternView.setOnPatternListener(null);
+ EmergencyButton button = mView.findViewById(R.id.emergency_call_button);
+ if (button != null) {
+ button.setCallback(null);
+ }
+ View cancelBtn = mView.findViewById(R.id.cancel_button);
+ if (cancelBtn != null) {
+ cancelBtn.setOnClickListener(null);
+ }
+ }
+
+ @Override
+ public void reset() {
+ // reset lock pattern
+ mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
+ KeyguardUpdateMonitor.getCurrentUser()));
+ mLockPatternView.enableInput();
+ mLockPatternView.setEnabled(true);
+ mLockPatternView.clearPattern();
+
+ // if the user is currently locked out, enforce it.
+ long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
+ KeyguardUpdateMonitor.getCurrentUser());
+ if (deadline != 0) {
+ handleAttemptLockout(deadline);
+ } else {
+ displayDefaultSecurityMessage();
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ if (mCountdownTimer != null) {
+ mCountdownTimer.cancel();
+ mCountdownTimer = null;
+ }
+
+ if (mPendingLockCheck != null) {
+ mPendingLockCheck.cancel(false);
+ mPendingLockCheck = null;
+ }
+ displayDefaultSecurityMessage();
+ }
+
+ @Override
+ public boolean needsInput() {
+ return false;
+ }
+
+ @Override
+ public void showPromptReason(int reason) {
+ /// TODO: move all this logic into the MessageAreaController?
+ switch (reason) {
+ case PROMPT_REASON_RESTART:
+ mMessageAreaController.setMessage(R.string.kg_prompt_reason_restart_pattern);
+ break;
+ case PROMPT_REASON_TIMEOUT:
+ mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
+ break;
+ case PROMPT_REASON_DEVICE_ADMIN:
+ mMessageAreaController.setMessage(R.string.kg_prompt_reason_device_admin);
+ break;
+ case PROMPT_REASON_USER_REQUEST:
+ mMessageAreaController.setMessage(R.string.kg_prompt_reason_user_request);
+ break;
+ case PROMPT_REASON_PREPARE_FOR_UPDATE:
+ mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
+ break;
+ case PROMPT_REASON_NONE:
+ break;
+ default:
+ mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
+ break;
+ }
+ }
+
+ @Override
+ public void showMessage(CharSequence message, ColorStateList colorState) {
+ if (colorState != null) {
+ mMessageAreaController.setNextMessageColor(colorState);
+ }
+ mMessageAreaController.setMessage(message);
+ }
+
+ @Override
+ public void startAppearAnimation() {
+ super.startAppearAnimation();
+ }
+
+ @Override
+ public boolean startDisappearAnimation(Runnable finishRunnable) {
+ return mView.startDisappearAnimation(
+ mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable);
+ }
+
+ private void displayDefaultSecurityMessage() {
+ mMessageAreaController.setMessage("");
+ }
+
+ private void handleAttemptLockout(long elapsedRealtimeDeadline) {
+ mLockPatternView.clearPattern();
+ mLockPatternView.setEnabled(false);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long secondsInFuture = (long) Math.ceil(
+ (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0);
+ mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) {
+
+ @Override
+ public void onTick(long millisUntilFinished) {
+ final int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0);
+ mMessageAreaController.setMessage(mView.getResources().getQuantityString(
+ R.plurals.kg_too_many_failed_attempts_countdown,
+ secondsRemaining, secondsRemaining));
+ }
+
+ @Override
+ public void onFinish() {
+ mLockPatternView.setEnabled(true);
+ displayDefaultSecurityMessage();
+ }
+
+ }.start();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index c7f27cf8a71a..7fa43116a7b1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -16,11 +16,17 @@
package com.android.keyguard;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
+
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.KeyEvent;
-import android.view.MotionEvent;
import android.view.View;
import com.android.internal.widget.LockscreenCredential;
@@ -29,22 +35,12 @@ import com.android.systemui.R;
/**
* A Pin based Keyguard input view
*/
-public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView
- implements View.OnKeyListener, View.OnTouchListener {
+public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView {
protected PasswordTextView mPasswordEntry;
private View mOkButton;
private View mDeleteButton;
- private View mButton0;
- private View mButton1;
- private View mButton2;
- private View mButton3;
- private View mButton4;
- private View mButton5;
- private View mButton6;
- private View mButton7;
- private View mButton8;
- private View mButton9;
+ private View[] mButtons = new View[10];
public KeyguardPinBasedInputView(Context context) {
this(context, null);
@@ -62,7 +58,6 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView
@Override
protected void resetState() {
- setPasswordEntryEnabled(true);
}
@Override
@@ -86,10 +81,10 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (KeyEvent.isConfirmKey(keyCode)) {
- performClick(mOkButton);
+ mOkButton.performClick();
return true;
} else if (keyCode == KeyEvent.KEYCODE_DEL) {
- performClick(mDeleteButton);
+ mDeleteButton.performClick();
return true;
}
if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
@@ -125,42 +120,9 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView
}
}
- private void performClick(View view) {
- view.performClick();
- }
-
private void performNumberClick(int number) {
- switch (number) {
- case 0:
- performClick(mButton0);
- break;
- case 1:
- performClick(mButton1);
- break;
- case 2:
- performClick(mButton2);
- break;
- case 3:
- performClick(mButton3);
- break;
- case 4:
- performClick(mButton4);
- break;
- case 5:
- performClick(mButton5);
- break;
- case 6:
- performClick(mButton6);
- break;
- case 7:
- performClick(mButton7);
- break;
- case 8:
- performClick(mButton8);
- break;
- case 9:
- performClick(mButton9);
- break;
+ if (number >= 0 && number <= 9) {
+ mButtons[number].performClick();
}
}
@@ -177,94 +139,31 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView
@Override
protected void onFinishInflate() {
mPasswordEntry = findViewById(getPasswordTextViewId());
- mPasswordEntry.setOnKeyListener(this);
// Set selected property on so the view can send accessibility events.
mPasswordEntry.setSelected(true);
- mPasswordEntry.setUserActivityListener(new PasswordTextView.UserActivityListener() {
- @Override
- public void onUserActivity() {
- onUserInput();
- }
- });
-
mOkButton = findViewById(R.id.key_enter);
- if (mOkButton != null) {
- mOkButton.setOnTouchListener(this);
- mOkButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (mPasswordEntry.isEnabled()) {
- verifyPasswordAndUnlock();
- }
- }
- });
- mOkButton.setOnHoverListener(new LiftToActivateListener(getContext()));
- }
mDeleteButton = findViewById(R.id.delete_button);
mDeleteButton.setVisibility(View.VISIBLE);
- mDeleteButton.setOnTouchListener(this);
- mDeleteButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- // check for time-based lockouts
- if (mPasswordEntry.isEnabled()) {
- mPasswordEntry.deleteLastChar();
- }
- }
- });
- mDeleteButton.setOnLongClickListener(new View.OnLongClickListener() {
- @Override
- public boolean onLongClick(View v) {
- // check for time-based lockouts
- if (mPasswordEntry.isEnabled()) {
- resetPasswordText(true /* animate */, true /* announce */);
- }
- doHapticKeyClick();
- return true;
- }
- });
- mButton0 = findViewById(R.id.key0);
- mButton1 = findViewById(R.id.key1);
- mButton2 = findViewById(R.id.key2);
- mButton3 = findViewById(R.id.key3);
- mButton4 = findViewById(R.id.key4);
- mButton5 = findViewById(R.id.key5);
- mButton6 = findViewById(R.id.key6);
- mButton7 = findViewById(R.id.key7);
- mButton8 = findViewById(R.id.key8);
- mButton9 = findViewById(R.id.key9);
+ mButtons[0] = findViewById(R.id.key0);
+ mButtons[1] = findViewById(R.id.key1);
+ mButtons[2] = findViewById(R.id.key2);
+ mButtons[3] = findViewById(R.id.key3);
+ mButtons[4] = findViewById(R.id.key4);
+ mButtons[5] = findViewById(R.id.key5);
+ mButtons[6] = findViewById(R.id.key6);
+ mButtons[7] = findViewById(R.id.key7);
+ mButtons[8] = findViewById(R.id.key8);
+ mButtons[9] = findViewById(R.id.key9);
mPasswordEntry.requestFocus();
super.onFinishInflate();
}
@Override
- public void onResume(int reason) {
- super.onResume(reason);
- mPasswordEntry.requestFocus();
- }
-
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- doHapticKeyClick();
- }
- return false;
- }
-
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- return onKeyDown(keyCode, event);
- }
- return false;
- }
-
- @Override
public CharSequence getTitle() {
return getContext().getString(
com.android.internal.R.string.keyguard_accessibility_pin_unlock);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
new file mode 100644
index 000000000000..4d0ebfffbe04
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.keyguard;
+
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnKeyListener;
+import android.view.View.OnTouchListener;
+
+import com.android.internal.util.LatencyTracker;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.R;
+
+public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinBasedInputView>
+ extends KeyguardAbsKeyInputViewController<T> {
+
+ private final LiftToActivateListener mLiftToActivateListener;
+ protected PasswordTextView mPasswordEntry;
+
+ private final OnKeyListener mOnKeyListener = (v, keyCode, event) -> {
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ return mView.onKeyDown(keyCode, event);
+ }
+ return false;
+ };
+
+ private final OnTouchListener mOnTouchListener = (v, event) -> {
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mView.doHapticKeyClick();
+ }
+ return false;
+ };
+
+ protected KeyguardPinBasedInputViewController(T view,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ SecurityMode securityMode,
+ LockPatternUtils lockPatternUtils,
+ KeyguardSecurityCallback keyguardSecurityCallback,
+ KeyguardMessageAreaController.Factory messageAreaControllerFactory,
+ LatencyTracker latencyTracker,
+ LiftToActivateListener liftToActivateListener) {
+ super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
+ messageAreaControllerFactory, latencyTracker);
+ mLiftToActivateListener = liftToActivateListener;
+ mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
+ }
+
+ @Override
+ protected void onViewAttached() {
+ super.onViewAttached();
+
+ mPasswordEntry.setOnKeyListener(mOnKeyListener);
+ mPasswordEntry.setUserActivityListener(this::onUserInput);
+
+ View deleteButton = mView.findViewById(R.id.delete_button);
+ deleteButton.setOnTouchListener(mOnTouchListener);
+ deleteButton.setOnClickListener(v -> {
+ // check for time-based lockouts
+ if (mPasswordEntry.isEnabled()) {
+ mPasswordEntry.deleteLastChar();
+ }
+ });
+ deleteButton.setOnLongClickListener(v -> {
+ // check for time-based lockouts
+ if (mPasswordEntry.isEnabled()) {
+ mView.resetPasswordText(true /* animate */, true /* announce */);
+ }
+ mView.doHapticKeyClick();
+ return true;
+ });
+
+ View okButton = mView.findViewById(R.id.key_enter);
+ if (okButton != null) {
+ okButton.setOnTouchListener(mOnTouchListener);
+ okButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mPasswordEntry.isEnabled()) {
+ verifyPasswordAndUnlock();
+ }
+ }
+ });
+ okButton.setOnHoverListener(mLiftToActivateListener);
+ }
+ }
+
+ @Override
+ public void onResume(int reason) {
+ super.onResume(reason);
+ mPasswordEntry.requestFocus();
+ }
+
+ @Override
+ void resetState() {
+ mView.setPasswordEntryEnabled(true);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
new file mode 100644
index 000000000000..6769436be8ef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -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.
+ */
+
+package com.android.keyguard;
+
+import android.view.View;
+
+import com.android.internal.util.LatencyTracker;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.R;
+
+public class KeyguardPinViewController
+ extends KeyguardPinBasedInputViewController<KeyguardPINView> {
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+
+ protected KeyguardPinViewController(KeyguardPINView view,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ SecurityMode securityMode, LockPatternUtils lockPatternUtils,
+ KeyguardSecurityCallback keyguardSecurityCallback,
+ KeyguardMessageAreaController.Factory messageAreaControllerFactory,
+ LatencyTracker latencyTracker,
+ LiftToActivateListener liftToActivateListener) {
+ super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
+ messageAreaControllerFactory, latencyTracker, liftToActivateListener);
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ }
+
+ @Override
+ protected void onViewAttached() {
+ super.onViewAttached();
+
+ View cancelBtn = mView.findViewById(R.id.cancel_button);
+ if (cancelBtn != null) {
+ cancelBtn.setOnClickListener(view -> {
+ getKeyguardSecurityCallback().reset();
+ getKeyguardSecurityCallback().onCancelClicked();
+ });
+ }
+ }
+
+ @Override
+ void resetState() {
+ super.resetState();
+ mMessageAreaController.setMessage("");
+ }
+
+ @Override
+ public boolean startDisappearAnimation(Runnable finishRunnable) {
+ return mView.startDisappearAnimation(
+ mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 05172279c4ed..b62ea6bc2ff6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -15,14 +15,10 @@
*/
package com.android.keyguard;
-import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
-import static android.view.ViewRootImpl.sNewInsetsMode;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.systemBars;
import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
-import static com.android.systemui.DejankUtils.whitelistIpcs;
-
import static java.lang.Integer.max;
import android.animation.Animator;
@@ -30,25 +26,14 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.app.AlertDialog;
-import android.app.admin.DevicePolicyManager;
import android.content.Context;
-import android.content.Intent;
-import android.content.res.ColorStateList;
import android.graphics.Insets;
import android.graphics.Rect;
-import android.metrics.LogMaker;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserHandle;
import android.util.AttributeSet;
-import android.util.Log;
import android.util.MathUtils;
-import android.util.Slog;
import android.util.TypedValue;
-import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.VelocityTracker;
-import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowInsets;
import android.view.WindowInsetsAnimation;
@@ -63,42 +48,30 @@ import androidx.annotation.VisibleForTesting;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringAnimation;
-import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.UiEventLoggerImpl;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.settingslib.utils.ThreadUtils;
-import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.SystemUIFactory;
-import com.android.systemui.shared.system.SysUiStatsLog;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.InjectionInflationController;
import java.util.List;
-public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSecurityView {
- private static final boolean DEBUG = KeyguardConstants.DEBUG;
- private static final String TAG = "KeyguardSecurityView";
-
- private static final int USER_TYPE_PRIMARY = 1;
- private static final int USER_TYPE_WORK_PROFILE = 2;
- private static final int USER_TYPE_SECONDARY_USER = 3;
+public class KeyguardSecurityContainer extends FrameLayout {
+ static final int USER_TYPE_PRIMARY = 1;
+ static final int USER_TYPE_WORK_PROFILE = 2;
+ static final int USER_TYPE_SECONDARY_USER = 3;
// Bouncer is dismissed due to no security.
- private static final int BOUNCER_DISMISS_NONE_SECURITY = 0;
+ static final int BOUNCER_DISMISS_NONE_SECURITY = 0;
// Bouncer is dismissed due to pin, password or pattern entered.
- private static final int BOUNCER_DISMISS_PASSWORD = 1;
+ static final int BOUNCER_DISMISS_PASSWORD = 1;
// Bouncer is dismissed due to biometric (face, fingerprint or iris) authenticated.
- private static final int BOUNCER_DISMISS_BIOMETRIC = 2;
+ static final int BOUNCER_DISMISS_BIOMETRIC = 2;
// Bouncer is dismissed due to extended access granted.
- private static final int BOUNCER_DISMISS_EXTENDED_ACCESS = 3;
+ static final int BOUNCER_DISMISS_EXTENDED_ACCESS = 3;
// Bouncer is dismissed due to sim card unlock code entered.
- private static final int BOUNCER_DISMISS_SIM = 4;
+ static final int BOUNCER_DISMISS_SIM = 4;
// Make the view move slower than the finger, as if the spring were applying force.
private static final float TOUCH_Y_MULTIPLIER = 0.25f;
@@ -107,36 +80,23 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
// How much to scale the default slop by, to avoid accidental drags.
private static final float SLOP_SCALE = 4f;
- private static final UiEventLogger sUiEventLogger = new UiEventLoggerImpl();
-
private static final long IME_DISAPPEAR_DURATION_MS = 125;
- private KeyguardSecurityModel mSecurityModel;
- private LockPatternUtils mLockPatternUtils;
-
@VisibleForTesting
KeyguardSecurityViewFlipper mSecurityViewFlipper;
- private boolean mIsVerifyUnlockOnly;
- private SecurityMode mCurrentSecuritySelection = SecurityMode.Invalid;
- private KeyguardSecurityView mCurrentSecurityView;
- private SecurityCallback mSecurityCallback;
private AlertDialog mAlertDialog;
- private InjectionInflationController mInjectionInflationController;
private boolean mSwipeUpToRetry;
- private AdminSecondaryLockScreenController mSecondaryLockScreenController;
private final ViewConfiguration mViewConfiguration;
private final SpringAnimation mSpringAnimation;
private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
- private final KeyguardUpdateMonitor mUpdateMonitor;
- private final KeyguardStateController mKeyguardStateController;
- private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
private float mLastTouchY = -1;
private int mActivePointerId = -1;
private boolean mIsDragging;
private float mStartTouchY = -1;
private boolean mDisappearAnimRunning;
+ private SwipeListener mSwipeListener;
private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
@@ -188,19 +148,22 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
// Used to notify the container when something interesting happens.
public interface SecurityCallback {
- public boolean dismiss(boolean authenticated, int targetUserId,
- boolean bypassSecondaryLockScreen);
- public void userActivity();
- public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput);
+ boolean dismiss(boolean authenticated, int targetUserId, boolean bypassSecondaryLockScreen);
+ void userActivity();
+ void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput);
/**
* @param strongAuth wheher the user has authenticated with strong authentication like
* pattern, password or PIN but not by trust agents or fingerprint
* @param targetUserId a user that needs to be the foreground user at the finish completion.
*/
- public void finish(boolean strongAuth, int targetUserId);
- public void reset();
- public void onCancelClicked();
+ void finish(boolean strongAuth, int targetUserId);
+ void reset();
+ void onCancelClicked();
+ }
+
+ public interface SwipeListener {
+ void onSwipeUp();
}
@VisibleForTesting
@@ -251,52 +214,24 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
public KeyguardSecurityContainer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- mSecurityModel = Dependency.get(KeyguardSecurityModel.class);
- mLockPatternUtils = new LockPatternUtils(context);
- mUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
mSpringAnimation = new SpringAnimation(this, DynamicAnimation.Y);
- mInjectionInflationController = new InjectionInflationController(
- SystemUIFactory.getInstance().getSysUIComponent().createViewInstanceCreatorFactory());
mViewConfiguration = ViewConfiguration.get(context);
- mKeyguardStateController = Dependency.get(KeyguardStateController.class);
- mSecondaryLockScreenController = new AdminSecondaryLockScreenController(context, this,
- mUpdateMonitor, mCallback, new Handler(Looper.myLooper()));
- }
-
- public void setSecurityCallback(SecurityCallback callback) {
- mSecurityCallback = callback;
}
- @Override
- public void onResume(int reason) {
- if (mCurrentSecuritySelection != SecurityMode.None) {
- getSecurityView(mCurrentSecuritySelection).onResume(reason);
- }
+ void onResume(SecurityMode securityMode, boolean faceAuthEnabled) {
mSecurityViewFlipper.setWindowInsetsAnimationCallback(mWindowInsetsAnimationCallback);
- updateBiometricRetry();
+ updateBiometricRetry(securityMode, faceAuthEnabled);
}
- @Override
public void onPause() {
if (mAlertDialog != null) {
mAlertDialog.dismiss();
mAlertDialog = null;
}
- mSecondaryLockScreenController.hide();
- if (mCurrentSecuritySelection != SecurityMode.None) {
- getSecurityView(mCurrentSecuritySelection).onPause();
- }
mSecurityViewFlipper.setWindowInsetsAnimationCallback(null);
}
@Override
- public void onStartingToHide() {
- if (mCurrentSecuritySelection != SecurityMode.None) {
- getSecurityView(mCurrentSecuritySelection).onStartingToHide();
- }
- }
-
- @Override
public boolean shouldDelayChildPressedState() {
return true;
}
@@ -318,13 +253,12 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
return false;
}
// Avoid dragging the pattern view
- if (mCurrentSecurityView.disallowInterceptTouch(event)) {
+ if (mSecurityViewFlipper.getSecurityView().disallowInterceptTouch(event)) {
return false;
}
int index = event.findPointerIndex(mActivePointerId);
float touchSlop = mViewConfiguration.getScaledTouchSlop() * SLOP_SCALE;
- if (mCurrentSecurityView != null && index != -1
- && mStartTouchY - event.getY(index) > touchSlop) {
+ if (index != -1 && mStartTouchY - event.getY(index) > touchSlop) {
mIsDragging = true;
return true;
}
@@ -372,31 +306,28 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
}
if (action == MotionEvent.ACTION_UP) {
if (-getTranslationY() > TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- MIN_DRAG_SIZE, getResources().getDisplayMetrics())
- && !mUpdateMonitor.isFaceDetectionRunning()) {
- mUpdateMonitor.requestFaceAuth();
- mCallback.userActivity();
- showMessage(null, null);
+ MIN_DRAG_SIZE, getResources().getDisplayMetrics())) {
+ if (mSwipeListener != null) {
+ mSwipeListener.onSwipeUp();
+ }
}
}
return true;
}
+ void setSwipeListener(SwipeListener swipeListener) {
+ mSwipeListener = swipeListener;
+ }
+
private void startSpringAnimation(float startVelocity) {
mSpringAnimation
.setStartVelocity(startVelocity)
.animateToFinalPosition(0);
}
- public void startAppearAnimation() {
- if (mCurrentSecuritySelection != SecurityMode.None) {
- getSecurityView(mCurrentSecuritySelection).startAppearAnimation();
- }
- }
-
- public boolean startDisappearAnimation(Runnable onFinishRunnable) {
+ public void startDisappearAnimation(SecurityMode securitySelection) {
mDisappearAnimRunning = true;
- if (mCurrentSecuritySelection == SecurityMode.Password) {
+ if (securitySelection == SecurityMode.Password) {
mSecurityViewFlipper.getWindowInsetsController().controlWindowInsetsAnimation(ime(),
IME_DISAPPEAR_DURATION_MS,
Interpolators.LINEAR, null, new WindowInsetsAnimationControlListener() {
@@ -441,19 +372,13 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
}
});
}
- if (mCurrentSecuritySelection != SecurityMode.None) {
- return getSecurityView(mCurrentSecuritySelection).startDisappearAnimation(
- onFinishRunnable);
- }
- return false;
}
/**
* Enables/disables swipe up to retry on the bouncer.
*/
- private void updateBiometricRetry() {
- SecurityMode securityMode = getSecurityMode();
- mSwipeUpToRetry = mKeyguardStateController.isFaceAuthEnabled()
+ private void updateBiometricRetry(SecurityMode securityMode, boolean faceAuthEnabled) {
+ mSwipeUpToRetry = faceAuthEnabled
&& securityMode != SecurityMode.SimPin
&& securityMode != SecurityMode.SimPuk
&& securityMode != SecurityMode.None;
@@ -463,67 +388,20 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
return mSecurityViewFlipper.getTitle();
}
- @VisibleForTesting
- protected KeyguardSecurityView getSecurityView(SecurityMode securityMode) {
- final int securityViewIdForMode = getSecurityViewIdForMode(securityMode);
- KeyguardSecurityView view = null;
- final int children = mSecurityViewFlipper.getChildCount();
- for (int child = 0; child < children; child++) {
- if (mSecurityViewFlipper.getChildAt(child).getId() == securityViewIdForMode) {
- view = ((KeyguardSecurityView)mSecurityViewFlipper.getChildAt(child));
- break;
- }
- }
- int layoutId = getLayoutIdFor(securityMode);
- if (view == null && layoutId != 0) {
- final LayoutInflater inflater = LayoutInflater.from(mContext);
- if (DEBUG) Log.v(TAG, "inflating id = " + layoutId);
- View v = mInjectionInflationController.injectable(inflater)
- .inflate(layoutId, mSecurityViewFlipper, false);
- mSecurityViewFlipper.addView(v);
- updateSecurityView(v);
- view = (KeyguardSecurityView)v;
- view.reset();
- }
-
- return view;
- }
-
- private void updateSecurityView(View view) {
- if (view instanceof KeyguardSecurityView) {
- KeyguardSecurityView ksv = (KeyguardSecurityView) view;
- ksv.setKeyguardCallback(mCallback);
- ksv.setLockPatternUtils(mLockPatternUtils);
- } else {
- Log.w(TAG, "View " + view + " is not a KeyguardSecurityView");
- }
- }
@Override
public void onFinishInflate() {
super.onFinishInflate();
mSecurityViewFlipper = findViewById(R.id.view_flipper);
- mSecurityViewFlipper.setLockPatternUtils(mLockPatternUtils);
- }
-
- public void setLockPatternUtils(LockPatternUtils utils) {
- mLockPatternUtils = utils;
- mSecurityModel.setLockPatternUtils(utils);
- mSecurityViewFlipper.setLockPatternUtils(mLockPatternUtils);
}
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
// Consume bottom insets because we're setting the padding locally (for IME and navbar.)
- int inset;
- if (sNewInsetsMode == NEW_INSETS_MODE_FULL) {
- int bottomInset = insets.getInsetsIgnoringVisibility(systemBars()).bottom;
- int imeInset = insets.getInsets(ime()).bottom;
- inset = max(bottomInset, imeInset);
- } else {
- inset = insets.getSystemWindowInsetBottom();
- }
+ int bottomInset = insets.getInsetsIgnoringVisibility(systemBars()).bottom;
+ int imeInset = insets.getInsets(ime()).bottom;
+ int inset = max(bottomInset, imeInset);
setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), inset);
return insets.inset(0, 0, 0, inset);
}
@@ -546,11 +424,12 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
mAlertDialog.show();
}
- private void showTimeoutDialog(int userId, int timeoutMs) {
- int timeoutInSeconds = (int) timeoutMs / 1000;
+ void showTimeoutDialog(int userId, int timeoutMs, LockPatternUtils lockPatternUtils,
+ SecurityMode securityMode) {
+ int timeoutInSeconds = timeoutMs / 1000;
int messageId = 0;
- switch (mSecurityModel.getSecurityMode(userId)) {
+ switch (securityMode) {
case Pattern:
messageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message;
break;
@@ -570,13 +449,13 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
if (messageId != 0) {
final String message = mContext.getString(messageId,
- mLockPatternUtils.getCurrentFailedPasswordAttempts(userId),
+ lockPatternUtils.getCurrentFailedPasswordAttempts(userId),
timeoutInSeconds);
showDialog(null, message);
}
}
- private void showAlmostAtWipeDialog(int attempts, int remaining, int userType) {
+ void showAlmostAtWipeDialog(int attempts, int remaining, int userType) {
String message = null;
switch (userType) {
case USER_TYPE_PRIMARY:
@@ -595,7 +474,7 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
showDialog(null, message);
}
- private void showWipeDialog(int attempts, int userType) {
+ void showWipeDialog(int attempts, int userType) {
String message = null;
switch (userType) {
case USER_TYPE_PRIMARY:
@@ -614,358 +493,8 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
showDialog(null, message);
}
- private void reportFailedUnlockAttempt(int userId, int timeoutMs) {
- // +1 for this time
- final int failedAttempts = mLockPatternUtils.getCurrentFailedPasswordAttempts(userId) + 1;
-
- if (DEBUG) Log.d(TAG, "reportFailedPatternAttempt: #" + failedAttempts);
-
- final DevicePolicyManager dpm = mLockPatternUtils.getDevicePolicyManager();
- final int failedAttemptsBeforeWipe =
- dpm.getMaximumFailedPasswordsForWipe(null, userId);
-
- final int remainingBeforeWipe = failedAttemptsBeforeWipe > 0 ?
- (failedAttemptsBeforeWipe - failedAttempts)
- : Integer.MAX_VALUE; // because DPM returns 0 if no restriction
- if (remainingBeforeWipe < LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE) {
- // The user has installed a DevicePolicyManager that requests a user/profile to be wiped
- // N attempts. Once we get below the grace period, we post this dialog every time as a
- // clear warning until the deletion fires.
- // Check which profile has the strictest policy for failed password attempts
- final int expiringUser = dpm.getProfileWithMinimumFailedPasswordsForWipe(userId);
- int userType = USER_TYPE_PRIMARY;
- if (expiringUser == userId) {
- // TODO: http://b/23522538
- if (expiringUser != UserHandle.USER_SYSTEM) {
- userType = USER_TYPE_SECONDARY_USER;
- }
- } else if (expiringUser != UserHandle.USER_NULL) {
- userType = USER_TYPE_WORK_PROFILE;
- } // If USER_NULL, which shouldn't happen, leave it as USER_TYPE_PRIMARY
- if (remainingBeforeWipe > 0) {
- showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe, userType);
- } else {
- // Too many attempts. The device will be wiped shortly.
- Slog.i(TAG, "Too many unlock attempts; user " + expiringUser + " will be wiped!");
- showWipeDialog(failedAttempts, userType);
- }
- }
- mLockPatternUtils.reportFailedPasswordAttempt(userId);
- if (timeoutMs > 0) {
- mLockPatternUtils.reportPasswordLockout(timeoutMs, userId);
- showTimeoutDialog(userId, timeoutMs);
- }
- }
-
- /**
- * Shows the primary security screen for the user. This will be either the multi-selector
- * or the user's security method.
- * @param turningOff true if the device is being turned off
- */
- void showPrimarySecurityScreen(boolean turningOff) {
- SecurityMode securityMode = whitelistIpcs(() -> mSecurityModel.getSecurityMode(
- KeyguardUpdateMonitor.getCurrentUser()));
- if (DEBUG) Log.v(TAG, "showPrimarySecurityScreen(turningOff=" + turningOff + ")");
- showSecurityScreen(securityMode);
- }
-
- /**
- * Shows the next security screen if there is one.
- * @param authenticated true if the user entered the correct authentication
- * @param targetUserId a user that needs to be the foreground user at the finish (if called)
- * completion.
- * @param bypassSecondaryLockScreen true if the user is allowed to bypass the secondary
- * secondary lock screen requirement, if any.
- * @return true if keyguard is done
- */
- boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetUserId,
- boolean bypassSecondaryLockScreen) {
- if (DEBUG) Log.d(TAG, "showNextSecurityScreenOrFinish(" + authenticated + ")");
- boolean finish = false;
- boolean strongAuth = false;
- int eventSubtype = -1;
- BouncerUiEvent uiEvent = BouncerUiEvent.UNKNOWN;
- if (mUpdateMonitor.getUserHasTrust(targetUserId)) {
- finish = true;
- eventSubtype = BOUNCER_DISMISS_EXTENDED_ACCESS;
- uiEvent = BouncerUiEvent.BOUNCER_DISMISS_EXTENDED_ACCESS;
- } else if (mUpdateMonitor.getUserUnlockedWithBiometric(targetUserId)) {
- finish = true;
- eventSubtype = BOUNCER_DISMISS_BIOMETRIC;
- uiEvent = BouncerUiEvent.BOUNCER_DISMISS_BIOMETRIC;
- } else if (SecurityMode.None == mCurrentSecuritySelection) {
- SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
- if (SecurityMode.None == securityMode) {
- finish = true; // no security required
- eventSubtype = BOUNCER_DISMISS_NONE_SECURITY;
- uiEvent = BouncerUiEvent.BOUNCER_DISMISS_NONE_SECURITY;
- } else {
- showSecurityScreen(securityMode); // switch to the alternate security view
- }
- } else if (authenticated) {
- switch (mCurrentSecuritySelection) {
- case Pattern:
- case Password:
- case PIN:
- strongAuth = true;
- finish = true;
- eventSubtype = BOUNCER_DISMISS_PASSWORD;
- uiEvent = BouncerUiEvent.BOUNCER_DISMISS_PASSWORD;
- break;
-
- case SimPin:
- case SimPuk:
- // Shortcut for SIM PIN/PUK to go to directly to user's security screen or home
- SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
- if (securityMode == SecurityMode.None && mLockPatternUtils.isLockScreenDisabled(
- KeyguardUpdateMonitor.getCurrentUser())) {
- finish = true;
- eventSubtype = BOUNCER_DISMISS_SIM;
- uiEvent = BouncerUiEvent.BOUNCER_DISMISS_SIM;
- } else {
- showSecurityScreen(securityMode);
- }
- break;
-
- default:
- Log.v(TAG, "Bad security screen " + mCurrentSecuritySelection + ", fail safe");
- showPrimarySecurityScreen(false);
- break;
- }
- }
- // Check for device admin specified additional security measures.
- if (finish && !bypassSecondaryLockScreen) {
- Intent secondaryLockscreenIntent =
- mUpdateMonitor.getSecondaryLockscreenRequirement(targetUserId);
- if (secondaryLockscreenIntent != null) {
- mSecondaryLockScreenController.show(secondaryLockscreenIntent);
- return false;
- }
- }
- if (eventSubtype != -1) {
- mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER)
- .setType(MetricsEvent.TYPE_DISMISS).setSubtype(eventSubtype));
- }
- if (uiEvent != BouncerUiEvent.UNKNOWN) {
- sUiEventLogger.log(uiEvent);
- }
- if (finish) {
- mSecurityCallback.finish(strongAuth, targetUserId);
- }
- return finish;
- }
-
- /**
- * Switches to the given security view unless it's already being shown, in which case
- * this is a no-op.
- *
- * @param securityMode
- */
- private void showSecurityScreen(SecurityMode securityMode) {
- if (DEBUG) Log.d(TAG, "showSecurityScreen(" + securityMode + ")");
-
- if (securityMode == mCurrentSecuritySelection) return;
-
- KeyguardSecurityView oldView = getSecurityView(mCurrentSecuritySelection);
- KeyguardSecurityView newView = getSecurityView(securityMode);
-
- // Emulate Activity life cycle
- if (oldView != null) {
- oldView.onPause();
- oldView.setKeyguardCallback(mNullCallback); // ignore requests from old view
- }
- if (securityMode != SecurityMode.None) {
- newView.onResume(KeyguardSecurityView.VIEW_REVEALED);
- newView.setKeyguardCallback(mCallback);
- }
-
- // Find and show this child.
- final int childCount = mSecurityViewFlipper.getChildCount();
-
- final int securityViewIdForMode = getSecurityViewIdForMode(securityMode);
- for (int i = 0; i < childCount; i++) {
- if (mSecurityViewFlipper.getChildAt(i).getId() == securityViewIdForMode) {
- mSecurityViewFlipper.setDisplayedChild(i);
- break;
- }
- }
-
- mCurrentSecuritySelection = securityMode;
- mCurrentSecurityView = newView;
- mSecurityCallback.onSecurityModeChanged(securityMode,
- securityMode != SecurityMode.None && newView.needsInput());
- }
-
- private KeyguardSecurityCallback mCallback = new KeyguardSecurityCallback() {
- public void userActivity() {
- if (mSecurityCallback != null) {
- mSecurityCallback.userActivity();
- }
- }
-
- @Override
- public void onUserInput() {
- mUpdateMonitor.cancelFaceAuth();
- }
-
- @Override
- public void dismiss(boolean authenticated, int targetId) {
- dismiss(authenticated, targetId, /* bypassSecondaryLockScreen */ false);
- }
-
- @Override
- public void dismiss(boolean authenticated, int targetId,
- boolean bypassSecondaryLockScreen) {
- mSecurityCallback.dismiss(authenticated, targetId, bypassSecondaryLockScreen);
- }
-
- public boolean isVerifyUnlockOnly() {
- return mIsVerifyUnlockOnly;
- }
-
- public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) {
- if (success) {
- SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED,
- SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__SUCCESS);
- mLockPatternUtils.reportSuccessfulPasswordAttempt(userId);
- // Force a garbage collection in an attempt to erase any lockscreen password left in
- // memory. Do it asynchronously with a 5-sec delay to avoid making the keyguard
- // dismiss animation janky.
- ThreadUtils.postOnBackgroundThread(() -> {
- try {
- Thread.sleep(5000);
- } catch (InterruptedException ignored) { }
- Runtime.getRuntime().gc();
- });
- } else {
- SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED,
- SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__FAILURE);
- KeyguardSecurityContainer.this.reportFailedUnlockAttempt(userId, timeoutMs);
- }
- mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER)
- .setType(success ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_FAILURE));
- sUiEventLogger.log(success ? BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS
- : BouncerUiEvent.BOUNCER_PASSWORD_FAILURE);
- }
-
- public void reset() {
- mSecurityCallback.reset();
- }
-
- public void onCancelClicked() {
- mSecurityCallback.onCancelClicked();
- }
- };
-
- // The following is used to ignore callbacks from SecurityViews that are no longer current
- // (e.g. face unlock). This avoids unwanted asynchronous events from messing with the
- // state for the current security method.
- private KeyguardSecurityCallback mNullCallback = new KeyguardSecurityCallback() {
- @Override
- public void userActivity() { }
- @Override
- public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) { }
- @Override
- public boolean isVerifyUnlockOnly() { return false; }
- @Override
- public void dismiss(boolean securityVerified, int targetUserId) { }
- @Override
- public void dismiss(boolean authenticated, int targetId,
- boolean bypassSecondaryLockScreen) { }
- @Override
- public void onUserInput() { }
- @Override
- public void reset() {}
- };
-
- private int getSecurityViewIdForMode(SecurityMode securityMode) {
- switch (securityMode) {
- case Pattern: return R.id.keyguard_pattern_view;
- case PIN: return R.id.keyguard_pin_view;
- case Password: return R.id.keyguard_password_view;
- case SimPin: return R.id.keyguard_sim_pin_view;
- case SimPuk: return R.id.keyguard_sim_puk_view;
- }
- return 0;
- }
-
- @VisibleForTesting
- public int getLayoutIdFor(SecurityMode securityMode) {
- switch (securityMode) {
- case Pattern: return R.layout.keyguard_pattern_view;
- case PIN: return R.layout.keyguard_pin_view;
- case Password: return R.layout.keyguard_password_view;
- case SimPin: return R.layout.keyguard_sim_pin_view;
- case SimPuk: return R.layout.keyguard_sim_puk_view;
- default:
- return 0;
- }
- }
-
- public SecurityMode getSecurityMode() {
- return mSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser());
- }
-
- public SecurityMode getCurrentSecurityMode() {
- return mCurrentSecuritySelection;
- }
-
- public KeyguardSecurityView getCurrentSecurityView() {
- return mCurrentSecurityView;
- }
-
- public void verifyUnlock() {
- mIsVerifyUnlockOnly = true;
- showSecurityScreen(getSecurityMode());
- }
-
- public SecurityMode getCurrentSecuritySelection() {
- return mCurrentSecuritySelection;
- }
-
- public void dismiss(boolean authenticated, int targetUserId) {
- mCallback.dismiss(authenticated, targetUserId);
- }
-
- public boolean needsInput() {
- return mSecurityViewFlipper.needsInput();
- }
-
- @Override
- public void setKeyguardCallback(KeyguardSecurityCallback callback) {
- mSecurityViewFlipper.setKeyguardCallback(callback);
- }
-
- @Override
public void reset() {
- mSecurityViewFlipper.reset();
mDisappearAnimRunning = false;
}
-
- @Override
- public KeyguardSecurityCallback getCallback() {
- return mSecurityViewFlipper.getCallback();
- }
-
- @Override
- public void showPromptReason(int reason) {
- if (mCurrentSecuritySelection != SecurityMode.None) {
- if (reason != PROMPT_REASON_NONE) {
- Log.i(TAG, "Strong auth required, reason: " + reason);
- }
- getSecurityView(mCurrentSecuritySelection).showPromptReason(reason);
- }
- }
-
- public void showMessage(CharSequence message, ColorStateList colorState) {
- if (mCurrentSecuritySelection != SecurityMode.None) {
- getSecurityView(mCurrentSecuritySelection).showMessage(message, colorState);
- }
- }
-
- @Override
- public void showUsabilityHint() {
- mSecurityViewFlipper.showUsabilityHint();
- }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 17f25bd08ef4..64676e55b038 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -16,33 +16,166 @@
package com.android.keyguard;
+import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC;
+import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_EXTENDED_ACCESS;
+import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_NONE_SECURITY;
+import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_PASSWORD;
+import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_SIM;
+import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_PRIMARY;
+import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_SECONDARY_USER;
+import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_WORK_PROFILE;
+import static com.android.systemui.DejankUtils.whitelistIpcs;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Intent;
import android.content.res.ColorStateList;
+import android.metrics.LogMaker;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardSecurityContainer.BouncerUiEvent;
import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback;
+import com.android.keyguard.KeyguardSecurityContainer.SwipeListener;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.keyguard.dagger.KeyguardBouncerScope;
+import com.android.settingslib.utils.ThreadUtils;
+import com.android.systemui.shared.system.SysUiStatsLog;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.ViewController;
import javax.inject.Inject;
/** Controller for {@link KeyguardSecurityContainer} */
-public class KeyguardSecurityContainerController extends ViewController<KeyguardSecurityContainer> {
+@KeyguardBouncerScope
+public class KeyguardSecurityContainerController extends ViewController<KeyguardSecurityContainer>
+ implements KeyguardSecurityView {
+
+ private static final boolean DEBUG = KeyguardConstants.DEBUG;
+ private static final String TAG = "KeyguardSecurityView";
+ private final AdminSecondaryLockScreenController mAdminSecondaryLockScreenController;
private final LockPatternUtils mLockPatternUtils;
- private final KeyguardSecurityViewController.Factory mKeyguardSecurityViewControllerFactory;
+ private final KeyguardUpdateMonitor mUpdateMonitor;
+ private final KeyguardSecurityModel mSecurityModel;
+ private final MetricsLogger mMetricsLogger;
+ private final UiEventLogger mUiEventLogger;
+ private final KeyguardStateController mKeyguardStateController;
+ private final KeyguardSecurityViewFlipperController mSecurityViewFlipperController;
+
+ private SecurityCallback mSecurityCallback;
+ private SecurityMode mCurrentSecurityMode = SecurityMode.Invalid;
+
+ private KeyguardSecurityCallback mKeyguardSecurityCallback = new KeyguardSecurityCallback() {
+ public void userActivity() {
+ if (mSecurityCallback != null) {
+ mSecurityCallback.userActivity();
+ }
+ }
+
+ @Override
+ public void onUserInput() {
+ mUpdateMonitor.cancelFaceAuth();
+ }
+
+ @Override
+ public void dismiss(boolean authenticated, int targetId) {
+ dismiss(authenticated, targetId, /* bypassSecondaryLockScreen */ false);
+ }
+
+ @Override
+ public void dismiss(boolean authenticated, int targetId,
+ boolean bypassSecondaryLockScreen) {
+ mSecurityCallback.dismiss(authenticated, targetId, bypassSecondaryLockScreen);
+ }
+
+ public boolean isVerifyUnlockOnly() {
+ return false;
+ }
+
+ public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) {
+ if (success) {
+ SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED,
+ SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__SUCCESS);
+ mLockPatternUtils.reportSuccessfulPasswordAttempt(userId);
+ // Force a garbage collection in an attempt to erase any lockscreen password left in
+ // memory. Do it asynchronously with a 5-sec delay to avoid making the keyguard
+ // dismiss animation janky.
+ ThreadUtils.postOnBackgroundThread(() -> {
+ try {
+ Thread.sleep(5000);
+ } catch (InterruptedException ignored) { }
+ Runtime.getRuntime().gc();
+ });
+ } else {
+ SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED,
+ SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__FAILURE);
+ reportFailedUnlockAttempt(userId, timeoutMs);
+ }
+ mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER)
+ .setType(success ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_FAILURE));
+ mUiEventLogger.log(success ? BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS
+ : BouncerUiEvent.BOUNCER_PASSWORD_FAILURE);
+ }
+
+ public void reset() {
+ mSecurityCallback.reset();
+ }
+
+ public void onCancelClicked() {
+ mSecurityCallback.onCancelClicked();
+ }
+ };
+
+
+ private SwipeListener mSwipeListener = new SwipeListener() {
+ @Override
+ public void onSwipeUp() {
+ if (!mUpdateMonitor.isFaceDetectionRunning()) {
+ mUpdateMonitor.requestFaceAuth();
+ mKeyguardSecurityCallback.userActivity();
+ showMessage(null, null);
+ }
+ }
+ };
@Inject
KeyguardSecurityContainerController(KeyguardSecurityContainer view,
+ AdminSecondaryLockScreenController.Factory adminSecondaryLockScreenControllerFactory,
LockPatternUtils lockPatternUtils,
- KeyguardSecurityViewController.Factory keyguardSecurityViewControllerFactory) {
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ KeyguardSecurityModel keyguardSecurityModel,
+ MetricsLogger metricsLogger,
+ UiEventLogger uiEventLogger,
+ KeyguardStateController keyguardStateController,
+ KeyguardSecurityViewFlipperController securityViewFlipperController) {
super(view);
mLockPatternUtils = lockPatternUtils;
- view.setLockPatternUtils(mLockPatternUtils);
- mKeyguardSecurityViewControllerFactory = keyguardSecurityViewControllerFactory;
+ mUpdateMonitor = keyguardUpdateMonitor;
+ mSecurityModel = keyguardSecurityModel;
+ mMetricsLogger = metricsLogger;
+ mUiEventLogger = uiEventLogger;
+ mKeyguardStateController = keyguardStateController;
+ mSecurityViewFlipperController = securityViewFlipperController;
+ mAdminSecondaryLockScreenController = adminSecondaryLockScreenControllerFactory.create(
+ mKeyguardSecurityCallback);
+ }
+
+ @Override
+ public void init() {
+ super.init();
+ mSecurityViewFlipperController.init();
}
@Override
protected void onViewAttached() {
+ mView.setSwipeListener(mSwipeListener);
}
@Override
@@ -51,68 +184,270 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
/** */
public void onPause() {
+ mAdminSecondaryLockScreenController.hide();
+ if (mCurrentSecurityMode != SecurityMode.None) {
+ getCurrentSecurityController().onPause();
+ }
mView.onPause();
}
+
+ /**
+ * Shows the primary security screen for the user. This will be either the multi-selector
+ * or the user's security method.
+ * @param turningOff true if the device is being turned off
+ */
public void showPrimarySecurityScreen(boolean turningOff) {
- mView.showPrimarySecurityScreen(turningOff);
+ SecurityMode securityMode = whitelistIpcs(() -> mSecurityModel.getSecurityMode(
+ KeyguardUpdateMonitor.getCurrentUser()));
+ if (DEBUG) Log.v(TAG, "showPrimarySecurityScreen(turningOff=" + turningOff + ")");
+ showSecurityScreen(securityMode);
}
+ @Override
public void showPromptReason(int reason) {
- mView.showPromptReason(reason);
+ if (mCurrentSecurityMode != SecurityMode.None) {
+ if (reason != PROMPT_REASON_NONE) {
+ Log.i(TAG, "Strong auth required, reason: " + reason);
+ }
+ getCurrentSecurityController().showPromptReason(reason);
+ }
}
public void showMessage(CharSequence message, ColorStateList colorState) {
- mView.showMessage(message, colorState);
+ if (mCurrentSecurityMode != SecurityMode.None) {
+ getCurrentSecurityController().showMessage(message, colorState);
+ }
}
- public SecurityMode getCurrentSecuritySelection() {
- return mView.getCurrentSecuritySelection();
+ public SecurityMode getCurrentSecurityMode() {
+ return mCurrentSecurityMode;
}
public void dismiss(boolean authenticated, int targetUserId) {
- mView.dismiss(authenticated, targetUserId);
+ mKeyguardSecurityCallback.dismiss(authenticated, targetUserId);
}
public void reset() {
mView.reset();
+ mSecurityViewFlipperController.reset();
}
public CharSequence getTitle() {
return mView.getTitle();
}
- public void onResume(int screenOn) {
- mView.onResume(screenOn);
+ @Override
+ public void onResume(int reason) {
+ if (mCurrentSecurityMode != SecurityMode.None) {
+ getCurrentSecurityController().onResume(reason);
+ }
+ mView.onResume(
+ mSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser()),
+ mKeyguardStateController.isFaceAuthEnabled());
}
public void startAppearAnimation() {
- mView.startAppearAnimation();
+ if (mCurrentSecurityMode != SecurityMode.None) {
+ getCurrentSecurityController().startAppearAnimation();
+ }
}
public boolean startDisappearAnimation(Runnable onFinishRunnable) {
- return mView.startDisappearAnimation(onFinishRunnable);
+ mView.startDisappearAnimation(getCurrentSecurityMode());
+
+ if (mCurrentSecurityMode != SecurityMode.None) {
+ return getCurrentSecurityController().startDisappearAnimation(onFinishRunnable);
+ }
+
+ return false;
}
public void onStartingToHide() {
- mView.onStartingToHide();
+ if (mCurrentSecurityMode != SecurityMode.None) {
+ getCurrentSecurityController().onStartingToHide();
+ }
}
public void setSecurityCallback(SecurityCallback securityCallback) {
- mView.setSecurityCallback(securityCallback);
+ mSecurityCallback = securityCallback;
}
+ /**
+ * Shows the next security screen if there is one.
+ * @param authenticated true if the user entered the correct authentication
+ * @param targetUserId a user that needs to be the foreground user at the finish (if called)
+ * completion.
+ * @param bypassSecondaryLockScreen true if the user is allowed to bypass the secondary
+ * secondary lock screen requirement, if any.
+ * @return true if keyguard is done
+ */
public boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetUserId,
boolean bypassSecondaryLockScreen) {
- return mView.showNextSecurityScreenOrFinish(
- authenticated, targetUserId, bypassSecondaryLockScreen);
+
+ if (DEBUG) Log.d(TAG, "showNextSecurityScreenOrFinish(" + authenticated + ")");
+ boolean finish = false;
+ boolean strongAuth = false;
+ int eventSubtype = -1;
+ BouncerUiEvent uiEvent = BouncerUiEvent.UNKNOWN;
+ if (mUpdateMonitor.getUserHasTrust(targetUserId)) {
+ finish = true;
+ eventSubtype = BOUNCER_DISMISS_EXTENDED_ACCESS;
+ uiEvent = BouncerUiEvent.BOUNCER_DISMISS_EXTENDED_ACCESS;
+ } else if (mUpdateMonitor.getUserUnlockedWithBiometric(targetUserId)) {
+ finish = true;
+ eventSubtype = BOUNCER_DISMISS_BIOMETRIC;
+ uiEvent = BouncerUiEvent.BOUNCER_DISMISS_BIOMETRIC;
+ } else if (SecurityMode.None == getCurrentSecurityMode()) {
+ SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
+ if (SecurityMode.None == securityMode) {
+ finish = true; // no security required
+ eventSubtype = BOUNCER_DISMISS_NONE_SECURITY;
+ uiEvent = BouncerUiEvent.BOUNCER_DISMISS_NONE_SECURITY;
+ } else {
+ showSecurityScreen(securityMode); // switch to the alternate security view
+ }
+ } else if (authenticated) {
+ switch (getCurrentSecurityMode()) {
+ case Pattern:
+ case Password:
+ case PIN:
+ strongAuth = true;
+ finish = true;
+ eventSubtype = BOUNCER_DISMISS_PASSWORD;
+ uiEvent = BouncerUiEvent.BOUNCER_DISMISS_PASSWORD;
+ break;
+
+ case SimPin:
+ case SimPuk:
+ // Shortcut for SIM PIN/PUK to go to directly to user's security screen or home
+ SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
+ if (securityMode == SecurityMode.None && mLockPatternUtils.isLockScreenDisabled(
+ KeyguardUpdateMonitor.getCurrentUser())) {
+ finish = true;
+ eventSubtype = BOUNCER_DISMISS_SIM;
+ uiEvent = BouncerUiEvent.BOUNCER_DISMISS_SIM;
+ } else {
+ showSecurityScreen(securityMode);
+ }
+ break;
+
+ default:
+ Log.v(TAG, "Bad security screen " + getCurrentSecurityMode()
+ + ", fail safe");
+ showPrimarySecurityScreen(false);
+ break;
+ }
+ }
+ // Check for device admin specified additional security measures.
+ if (finish && !bypassSecondaryLockScreen) {
+ Intent secondaryLockscreenIntent =
+ mUpdateMonitor.getSecondaryLockscreenRequirement(targetUserId);
+ if (secondaryLockscreenIntent != null) {
+ mAdminSecondaryLockScreenController.show(secondaryLockscreenIntent);
+ return false;
+ }
+ }
+ if (eventSubtype != -1) {
+ mMetricsLogger.write(new LogMaker(MetricsProto.MetricsEvent.BOUNCER)
+ .setType(MetricsProto.MetricsEvent.TYPE_DISMISS).setSubtype(eventSubtype));
+ }
+ if (uiEvent != BouncerUiEvent.UNKNOWN) {
+ mUiEventLogger.log(uiEvent);
+ }
+ if (finish) {
+ mSecurityCallback.finish(strongAuth, targetUserId);
+ }
+ return finish;
}
public boolean needsInput() {
- return mView.needsInput();
+ return getCurrentSecurityController().needsInput();
}
- public SecurityMode getCurrentSecurityMode() {
- return mView.getCurrentSecurityMode();
+ /**
+ * Switches to the given security view unless it's already being shown, in which case
+ * this is a no-op.
+ *
+ * @param securityMode
+ */
+ @VisibleForTesting
+ void showSecurityScreen(SecurityMode securityMode) {
+ if (DEBUG) Log.d(TAG, "showSecurityScreen(" + securityMode + ")");
+
+ if (securityMode == SecurityMode.Invalid || securityMode == mCurrentSecurityMode) {
+ return;
+ }
+
+ KeyguardInputViewController<KeyguardInputView> oldView = getCurrentSecurityController();
+
+ // Emulate Activity life cycle
+ if (oldView != null) {
+ oldView.onPause();
+ }
+
+ KeyguardInputViewController<KeyguardInputView> newView = changeSecurityMode(securityMode);
+ if (newView != null) {
+ newView.onResume(KeyguardSecurityView.VIEW_REVEALED);
+ mSecurityViewFlipperController.show(newView);
+ }
+
+ mSecurityCallback.onSecurityModeChanged(
+ securityMode, newView != null && newView.needsInput());
+ }
+
+ public void reportFailedUnlockAttempt(int userId, int timeoutMs) {
+ // +1 for this time
+ final int failedAttempts = mLockPatternUtils.getCurrentFailedPasswordAttempts(userId) + 1;
+
+ if (DEBUG) Log.d(TAG, "reportFailedPatternAttempt: #" + failedAttempts);
+
+ final DevicePolicyManager dpm = mLockPatternUtils.getDevicePolicyManager();
+ final int failedAttemptsBeforeWipe =
+ dpm.getMaximumFailedPasswordsForWipe(null, userId);
+
+ final int remainingBeforeWipe = failedAttemptsBeforeWipe > 0
+ ? (failedAttemptsBeforeWipe - failedAttempts)
+ : Integer.MAX_VALUE; // because DPM returns 0 if no restriction
+ if (remainingBeforeWipe < LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE) {
+ // The user has installed a DevicePolicyManager that requests a user/profile to be wiped
+ // N attempts. Once we get below the grace period, we post this dialog every time as a
+ // clear warning until the deletion fires.
+ // Check which profile has the strictest policy for failed password attempts
+ final int expiringUser = dpm.getProfileWithMinimumFailedPasswordsForWipe(userId);
+ int userType = USER_TYPE_PRIMARY;
+ if (expiringUser == userId) {
+ // TODO: http://b/23522538
+ if (expiringUser != UserHandle.USER_SYSTEM) {
+ userType = USER_TYPE_SECONDARY_USER;
+ }
+ } else if (expiringUser != UserHandle.USER_NULL) {
+ userType = USER_TYPE_WORK_PROFILE;
+ } // If USER_NULL, which shouldn't happen, leave it as USER_TYPE_PRIMARY
+ if (remainingBeforeWipe > 0) {
+ mView.showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe, userType);
+ } else {
+ // Too many attempts. The device will be wiped shortly.
+ Slog.i(TAG, "Too many unlock attempts; user " + expiringUser + " will be wiped!");
+ mView.showWipeDialog(failedAttempts, userType);
+ }
+ }
+ mLockPatternUtils.reportFailedPasswordAttempt(userId);
+ if (timeoutMs > 0) {
+ mLockPatternUtils.reportPasswordLockout(timeoutMs, userId);
+ mView.showTimeoutDialog(userId, timeoutMs, mLockPatternUtils,
+ mSecurityModel.getSecurityMode(userId));
+ }
+ }
+
+ private KeyguardInputViewController<KeyguardInputView> getCurrentSecurityController() {
+ return mSecurityViewFlipperController
+ .getSecurityView(mCurrentSecurityMode, mKeyguardSecurityCallback);
+ }
+
+ private KeyguardInputViewController<KeyguardInputView> changeSecurityMode(
+ SecurityMode securityMode) {
+ mCurrentSecurityMode = securityMode;
+ return getCurrentSecurityController();
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
index ac2160ecb4ae..c77c86711abf 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
@@ -18,13 +18,14 @@ package com.android.keyguard;
import static com.android.systemui.DejankUtils.whitelistIpcs;
import android.app.admin.DevicePolicyManager;
-import android.content.Context;
+import android.content.res.Resources;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.Dependency;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import javax.inject.Inject;
@@ -33,7 +34,7 @@ public class KeyguardSecurityModel {
/**
* The different types of security available.
- * @see KeyguardSecurityContainer#showSecurityScreen
+ * @see KeyguardSecurityContainerController#showSecurityScreen
*/
public enum SecurityMode {
Invalid, // NULL state
@@ -45,21 +46,15 @@ public class KeyguardSecurityModel {
SimPuk // Unlock by entering a sim puk
}
- private final Context mContext;
private final boolean mIsPukScreenAvailable;
- private LockPatternUtils mLockPatternUtils;
+ private final LockPatternUtils mLockPatternUtils;
@Inject
- KeyguardSecurityModel(Context context) {
- mContext = context;
- mLockPatternUtils = new LockPatternUtils(context);
- mIsPukScreenAvailable = mContext.getResources().getBoolean(
+ KeyguardSecurityModel(@Main Resources resources, LockPatternUtils lockPatternUtils) {
+ mIsPukScreenAvailable = resources.getBoolean(
com.android.internal.R.bool.config_enable_puk_unlock_screen);
- }
-
- void setLockPatternUtils(LockPatternUtils utils) {
- mLockPatternUtils = utils;
+ mLockPatternUtils = lockPatternUtils;
}
public SecurityMode getSecurityMode(int userId) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
index 43cef3acf147..ac00e9453c97 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
@@ -18,11 +18,9 @@ package com.android.keyguard;
import android.content.res.ColorStateList;
import android.view.MotionEvent;
-import com.android.internal.widget.LockPatternUtils;
-
public interface KeyguardSecurityView {
- static public final int SCREEN_ON = 1;
- static public final int VIEW_REVEALED = 2;
+ int SCREEN_ON = 1;
+ int VIEW_REVEALED = 2;
int PROMPT_REASON_NONE = 0;
@@ -63,18 +61,6 @@ public interface KeyguardSecurityView {
int PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT = 7;
/**
- * Interface back to keyguard to tell it when security
- * @param callback
- */
- void setKeyguardCallback(KeyguardSecurityCallback callback);
-
- /**
- * Set {@link LockPatternUtils} object. Useful for providing a mock interface.
- * @param utils
- */
- void setLockPatternUtils(LockPatternUtils utils);
-
- /**
* Reset the view and prepare to take input. This should do things like clearing the
* password or pattern and clear error messages.
*/
@@ -101,12 +87,6 @@ public interface KeyguardSecurityView {
boolean needsInput();
/**
- * Get {@link KeyguardSecurityCallback} for the given object
- * @return KeyguardSecurityCallback
- */
- KeyguardSecurityCallback getCallback();
-
- /**
* Show a string explaining why the security view needs to be solved.
*
* @param reason a flag indicating which string should be shown, see {@link #PROMPT_REASON_NONE}
@@ -123,12 +103,6 @@ public interface KeyguardSecurityView {
void showMessage(CharSequence message, ColorStateList colorState);
/**
- * Instruct the view to show usability hints, if any.
- *
- */
- void showUsabilityHint();
-
- /**
* Starts the animation which should run when the security view appears.
*/
void startAppearAnimation();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewController.java
deleted file mode 100644
index ef9ba19fbb43..000000000000
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewController.java
+++ /dev/null
@@ -1,58 +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.keyguard;
-
-import android.view.View;
-
-import com.android.systemui.util.ViewController;
-
-import javax.inject.Inject;
-
-
-/** Controller for a {@link KeyguardSecurityView}. */
-public class KeyguardSecurityViewController extends ViewController<View> {
-
- private final KeyguardSecurityView mView;
-
- private KeyguardSecurityViewController(KeyguardSecurityView view) {
- super((View) view);
- // KeyguardSecurityView isn't actually a View, so we need to track it ourselves.
- mView = view;
- }
-
- @Override
- protected void onViewAttached() {
-
- }
-
- @Override
- protected void onViewDetached() {
-
- }
-
- /** Factory for a {@link KeyguardSecurityViewController}. */
- public static class Factory {
- @Inject
- public Factory() {
- }
-
- /** Create a new {@link KeyguardSecurityViewController}. */
- public KeyguardSecurityViewController create(KeyguardSecurityView view) {
- return new KeyguardSecurityViewController(view);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
index 24da3ad46f23..b8439af6daaa 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
@@ -18,7 +18,6 @@ package com.android.keyguard;
import android.annotation.NonNull;
import android.content.Context;
-import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.util.AttributeSet;
@@ -31,7 +30,6 @@ import android.view.ViewHierarchyEncoder;
import android.widget.FrameLayout;
import android.widget.ViewFlipper;
-import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.R;
/**
@@ -39,7 +37,7 @@ import com.android.systemui.R;
* we can emulate {@link android.view.WindowManager.LayoutParams#FLAG_SLIPPERY} within a view
* hierarchy.
*/
-public class KeyguardSecurityViewFlipper extends ViewFlipper implements KeyguardSecurityView {
+public class KeyguardSecurityViewFlipper extends ViewFlipper {
private static final String TAG = "KeyguardSecurityViewFlipper";
private static final boolean DEBUG = KeyguardConstants.DEBUG;
@@ -69,111 +67,16 @@ public class KeyguardSecurityViewFlipper extends ViewFlipper implements Keyguard
return result;
}
- KeyguardSecurityView getSecurityView() {
+ KeyguardInputView getSecurityView() {
View child = getChildAt(getDisplayedChild());
- if (child instanceof KeyguardSecurityView) {
- return (KeyguardSecurityView) child;
+ if (child instanceof KeyguardInputView) {
+ return (KeyguardInputView) child;
}
return null;
}
- @Override
- public void setKeyguardCallback(KeyguardSecurityCallback callback) {
- KeyguardSecurityView ksv = getSecurityView();
- if (ksv != null) {
- ksv.setKeyguardCallback(callback);
- }
- }
-
- @Override
- public void setLockPatternUtils(LockPatternUtils utils) {
- KeyguardSecurityView ksv = getSecurityView();
- if (ksv != null) {
- ksv.setLockPatternUtils(utils);
- }
- }
-
- @Override
- public void reset() {
- KeyguardSecurityView ksv = getSecurityView();
- if (ksv != null) {
- ksv.reset();
- }
- }
-
- @Override
- public void onPause() {
- KeyguardSecurityView ksv = getSecurityView();
- if (ksv != null) {
- ksv.onPause();
- }
- }
-
- @Override
- public void onResume(int reason) {
- KeyguardSecurityView ksv = getSecurityView();
- if (ksv != null) {
- ksv.onResume(reason);
- }
- }
-
- @Override
- public boolean needsInput() {
- KeyguardSecurityView ksv = getSecurityView();
- return (ksv != null) ? ksv.needsInput() : false;
- }
-
- @Override
- public KeyguardSecurityCallback getCallback() {
- KeyguardSecurityView ksv = getSecurityView();
- return (ksv != null) ? ksv.getCallback() : null;
- }
-
- @Override
- public void showPromptReason(int reason) {
- KeyguardSecurityView ksv = getSecurityView();
- if (ksv != null) {
- ksv.showPromptReason(reason);
- }
- }
-
- @Override
- public void showMessage(CharSequence message, ColorStateList colorState) {
- KeyguardSecurityView ksv = getSecurityView();
- if (ksv != null) {
- ksv.showMessage(message, colorState);
- }
- }
-
- @Override
- public void showUsabilityHint() {
- KeyguardSecurityView ksv = getSecurityView();
- if (ksv != null) {
- ksv.showUsabilityHint();
- }
- }
-
- @Override
- public void startAppearAnimation() {
- KeyguardSecurityView ksv = getSecurityView();
- if (ksv != null) {
- ksv.startAppearAnimation();
- }
- }
-
- @Override
- public boolean startDisappearAnimation(Runnable finishRunnable) {
- KeyguardSecurityView ksv = getSecurityView();
- if (ksv != null) {
- return ksv.startDisappearAnimation(finishRunnable);
- } else {
- return false;
- }
- }
-
- @Override
public CharSequence getTitle() {
- KeyguardSecurityView ksv = getSecurityView();
+ KeyguardInputView ksv = getSecurityView();
if (ksv != null) {
return ksv.getTitle();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
new file mode 100644
index 000000000000..49530355a6fb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.keyguard;
+
+import android.util.Log;
+import android.view.LayoutInflater;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.keyguard.KeyguardInputViewController.Factory;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.keyguard.dagger.KeyguardBouncerScope;
+import com.android.systemui.R;
+import com.android.systemui.util.ViewController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+
+/**
+ * Controller for a {@link KeyguardSecurityViewFlipper}.
+ */
+@KeyguardBouncerScope
+public class KeyguardSecurityViewFlipperController
+ extends ViewController<KeyguardSecurityViewFlipper> {
+
+ private static final boolean DEBUG = KeyguardConstants.DEBUG;
+ private static final String TAG = "KeyguardSecurityView";
+
+ private final List<KeyguardInputViewController<KeyguardInputView>> mChildren =
+ new ArrayList<>();
+ private final LayoutInflater mLayoutInflater;
+ private final Factory mKeyguardSecurityViewControllerFactory;
+
+ @Inject
+ protected KeyguardSecurityViewFlipperController(KeyguardSecurityViewFlipper view,
+ LayoutInflater layoutInflater,
+ KeyguardInputViewController.Factory keyguardSecurityViewControllerFactory) {
+ super(view);
+ mKeyguardSecurityViewControllerFactory = keyguardSecurityViewControllerFactory;
+ mLayoutInflater = layoutInflater;
+ }
+
+ @Override
+ protected void onViewAttached() {
+
+ }
+
+ @Override
+ protected void onViewDetached() {
+
+ }
+
+ public void reset() {
+ for (KeyguardInputViewController<KeyguardInputView> child : mChildren) {
+ child.reset();
+ }
+ }
+
+ @VisibleForTesting
+ KeyguardInputViewController<KeyguardInputView> getSecurityView(SecurityMode securityMode,
+ KeyguardSecurityCallback keyguardSecurityCallback) {
+ KeyguardInputViewController<KeyguardInputView> childController = null;
+ for (KeyguardInputViewController<KeyguardInputView> child : mChildren) {
+ if (child.getSecurityMode() == securityMode) {
+ childController = child;
+ break;
+ }
+ }
+
+ if (childController == null
+ && securityMode != SecurityMode.None && securityMode != SecurityMode.Invalid) {
+
+ int layoutId = getLayoutIdFor(securityMode);
+ KeyguardInputView view = null;
+ if (layoutId != 0) {
+ if (DEBUG) Log.v(TAG, "inflating id = " + layoutId);
+ view = (KeyguardInputView) mLayoutInflater.inflate(
+ layoutId, mView, false);
+ mView.addView(view);
+ childController = mKeyguardSecurityViewControllerFactory.create(
+ view, securityMode, keyguardSecurityCallback);
+ childController.init();
+
+ mChildren.add(childController);
+ }
+ }
+
+ if (childController == null) {
+ childController = new NullKeyguardInputViewController(
+ securityMode, keyguardSecurityCallback);
+ }
+
+ return childController;
+ }
+
+ private int getLayoutIdFor(SecurityMode securityMode) {
+ switch (securityMode) {
+ case Pattern: return com.android.systemui.R.layout.keyguard_pattern_view;
+ case PIN: return com.android.systemui.R.layout.keyguard_pin_view;
+ case Password: return com.android.systemui.R.layout.keyguard_password_view;
+ case SimPin: return com.android.systemui.R.layout.keyguard_sim_pin_view;
+ case SimPuk: return R.layout.keyguard_sim_puk_view;
+ default:
+ return 0;
+ }
+ }
+
+ /** Makes the supplied child visible if it is contained win this view, */
+ public void show(KeyguardInputViewController<KeyguardInputView> childController) {
+ int index = childController.getIndexIn(mView);
+ if (index != -1) {
+ mView.setDisplayedChild(index);
+ }
+ }
+
+ private static class NullKeyguardInputViewController
+ extends KeyguardInputViewController<KeyguardInputView> {
+ protected NullKeyguardInputViewController(SecurityMode securityMode,
+ KeyguardSecurityCallback keyguardSecurityCallback) {
+ super(null, securityMode, keyguardSecurityCallback);
+ }
+
+ @Override
+ public boolean needsInput() {
+ return false;
+ }
+
+ @Override
+ public void onStartingToHide() {
+
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
index 1c47aa0151f0..c0f9ce794628 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
@@ -16,66 +16,19 @@
package com.android.keyguard;
-import android.annotation.NonNull;
-import android.app.AlertDialog;
-import android.app.AlertDialog.Builder;
-import android.app.Dialog;
-import android.app.ProgressDialog;
import android.content.Context;
-import android.content.res.ColorStateList;
import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Color;
-import android.telephony.PinResult;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.View;
-import android.view.WindowManager;
-import android.widget.ImageView;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
/**
* Displays a PIN pad for unlocking.
*/
public class KeyguardSimPinView extends KeyguardPinBasedInputView {
- private static final String LOG_TAG = "KeyguardSimPinView";
- private static final boolean DEBUG = KeyguardConstants.DEBUG_SIM_STATES;
public static final String TAG = "KeyguardSimPinView";
- private ProgressDialog mSimUnlockProgressDialog = null;
- private CheckSimPin mCheckSimPinThread;
-
- // Below flag is set to true during power-up or when a new SIM card inserted on device.
- // When this is true and when SIM card is PIN locked state, on PIN lock screen, message would
- // be displayed to inform user about the number of remaining PIN attempts left.
- private boolean mShowDefaultMessage = true;
- private int mRemainingAttempts = -1;
- private AlertDialog mRemainingAttemptsDialog;
- private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
- private ImageView mSimImageView;
-
- KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() {
- @Override
- public void onSimStateChanged(int subId, int slotId, int simState) {
- if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")");
- switch(simState) {
- case TelephonyManager.SIM_STATE_READY: {
- mRemainingAttempts = -1;
- resetState();
- break;
- }
- default:
- resetState();
- }
- }
- };
-
public KeyguardSimPinView(Context context) {
this(context, null);
}
@@ -84,81 +37,9 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView {
super(context, attrs);
}
- @Override
- public void resetState() {
- super.resetState();
- if (DEBUG) Log.v(TAG, "Resetting state");
- handleSubInfoChangeIfNeeded();
- if (mShowDefaultMessage) {
- showDefaultMessage();
- }
- boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId);
-
+ public void setEsimLocked(boolean locked) {
KeyguardEsimArea esimButton = findViewById(R.id.keyguard_esim_area);
- esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE);
- }
-
- private void setLockedSimMessage() {
- boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId);
- int count = 1;
- TelephonyManager telephonyManager =
- (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
- if (telephonyManager != null) {
- count = telephonyManager.getActiveModemCount();
- }
- Resources rez = getResources();
- String msg;
- TypedArray array = mContext.obtainStyledAttributes(new int[] { R.attr.wallpaperTextColor });
- int color = array.getColor(0, Color.WHITE);
- array.recycle();
- if (count < 2) {
- msg = rez.getString(R.string.kg_sim_pin_instructions);
- } else {
- SubscriptionInfo info = Dependency.get(KeyguardUpdateMonitor.class)
- .getSubscriptionInfoForSubId(mSubId);
- CharSequence displayName = info != null ? info.getDisplayName() : ""; // don't crash
- msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName);
- if (info != null) {
- color = info.getIconTint();
- }
- }
- if (isEsimLocked) {
- msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg);
- }
-
- if (mSecurityMessageDisplay != null && getVisibility() == VISIBLE) {
- mSecurityMessageDisplay.setMessage(msg);
- }
- mSimImageView.setImageTintList(ColorStateList.valueOf(color));
- }
-
- private void showDefaultMessage() {
- setLockedSimMessage();
- if (mRemainingAttempts >= 0) {
- return;
- }
-
- // Sending empty PIN here to query the number of remaining PIN attempts
- new CheckSimPin("", mSubId) {
- void onSimCheckResponse(final PinResult result) {
- Log.d(LOG_TAG, "onSimCheckResponse " + " empty One result "
- + result.toString());
- if (result.getAttemptsRemaining() >= 0) {
- mRemainingAttempts = result.getAttemptsRemaining();
- setLockedSimMessage();
- }
- }
- }.start();
- }
-
- private void handleSubInfoChangeIfNeeded() {
- KeyguardUpdateMonitor monitor = Dependency.get(KeyguardUpdateMonitor.class);
- int subId = monitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PIN_REQUIRED);
- if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) {
- mSubId = subId;
- mShowDefaultMessage = true;
- mRemainingAttempts = -1;
- }
+ esimButton.setVisibility(locked ? View.VISIBLE : View.GONE);
}
@Override
@@ -173,35 +54,6 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView {
return 0;
}
- private String getPinPasswordErrorMessage(int attemptsRemaining, boolean isDefault) {
- String displayMessage;
- int msgId;
- if (attemptsRemaining == 0) {
- displayMessage = getContext().getString(R.string.kg_password_wrong_pin_code_pukked);
- } else if (attemptsRemaining > 0) {
- msgId = isDefault ? R.plurals.kg_password_default_pin_message :
- R.plurals.kg_password_wrong_pin_code;
- displayMessage = getContext().getResources()
- .getQuantityString(msgId, attemptsRemaining, attemptsRemaining);
- } else {
- msgId = isDefault ? R.string.kg_sim_pin_instructions : R.string.kg_password_pin_failed;
- displayMessage = getContext().getString(msgId);
- }
- if (KeyguardEsimArea.isEsimLocked(mContext, mSubId)) {
- displayMessage = getResources()
- .getString(R.string.kg_sim_lock_esim_instructions, displayMessage);
- }
- if (DEBUG) Log.d(LOG_TAG, "getPinPasswordErrorMessage:"
- + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage);
- return displayMessage;
- }
-
- @Override
- protected boolean shouldLockout(long deadline) {
- // SIM PIN doesn't have a timed lockout
- return false;
- }
-
@Override
protected int getPasswordTextViewId() {
return R.id.simPinEntry;
@@ -214,173 +66,6 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView {
if (mEcaView instanceof EmergencyCarrierArea) {
((EmergencyCarrierArea) mEcaView).setCarrierTextVisible(true);
}
- mSimImageView = findViewById(R.id.keyguard_sim);
- }
-
- @Override
- public void showUsabilityHint() {
-
- }
-
- @Override
- public void onResume(int reason) {
- super.onResume(reason);
- Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mUpdateMonitorCallback);
- resetState();
- }
-
- @Override
- public void onPause() {
- // dismiss the dialog.
- if (mSimUnlockProgressDialog != null) {
- mSimUnlockProgressDialog.dismiss();
- mSimUnlockProgressDialog = null;
- }
- Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mUpdateMonitorCallback);
- }
-
- /**
- * Since the IPC can block, we want to run the request in a separate thread
- * with a callback.
- */
- private abstract class CheckSimPin extends Thread {
- private final String mPin;
- private int mSubId;
-
- protected CheckSimPin(String pin, int subId) {
- mPin = pin;
- mSubId = subId;
- }
-
- abstract void onSimCheckResponse(@NonNull PinResult result);
-
- @Override
- public void run() {
- if (DEBUG) {
- Log.v(TAG, "call supplyPinReportResultForSubscriber(subid=" + mSubId + ")");
- }
- TelephonyManager telephonyManager =
- ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE))
- .createForSubscriptionId(mSubId);
- final PinResult result = telephonyManager.supplyPinReportPinResult(mPin);
- if (result == null) {
- Log.e(TAG, "Error result for supplyPinReportResult.");
- post(new Runnable() {
- @Override
- public void run() {
- onSimCheckResponse(PinResult.getDefaultFailedResult());
- }
- });
- } else {
- if (DEBUG) {
- Log.v(TAG, "supplyPinReportResult returned: " + result.toString());
- }
- post(new Runnable() {
- @Override
- public void run() {
- onSimCheckResponse(result);
- }
- });
- }
- }
- }
-
- private Dialog getSimUnlockProgressDialog() {
- if (mSimUnlockProgressDialog == null) {
- mSimUnlockProgressDialog = new ProgressDialog(mContext);
- mSimUnlockProgressDialog.setMessage(
- mContext.getString(R.string.kg_sim_unlock_progress_dialog_message));
- mSimUnlockProgressDialog.setIndeterminate(true);
- mSimUnlockProgressDialog.setCancelable(false);
- mSimUnlockProgressDialog.getWindow().setType(
- WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
- }
- return mSimUnlockProgressDialog;
- }
-
- private Dialog getSimRemainingAttemptsDialog(int remaining) {
- String msg = getPinPasswordErrorMessage(remaining, false);
- if (mRemainingAttemptsDialog == null) {
- Builder builder = new AlertDialog.Builder(mContext);
- builder.setMessage(msg);
- builder.setCancelable(false);
- builder.setNeutralButton(R.string.ok, null);
- mRemainingAttemptsDialog = builder.create();
- mRemainingAttemptsDialog.getWindow().setType(
- WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
- } else {
- mRemainingAttemptsDialog.setMessage(msg);
- }
- return mRemainingAttemptsDialog;
- }
-
- @Override
- protected void verifyPasswordAndUnlock() {
- String entry = mPasswordEntry.getText();
-
- if (entry.length() < 4) {
- // otherwise, display a message to the user, and don't submit.
- mSecurityMessageDisplay.setMessage(R.string.kg_invalid_sim_pin_hint);
- resetPasswordText(true /* animate */, true /* announce */);
- mCallback.userActivity();
- return;
- }
-
- getSimUnlockProgressDialog().show();
-
- if (mCheckSimPinThread == null) {
- mCheckSimPinThread = new CheckSimPin(mPasswordEntry.getText(), mSubId) {
- @Override
- void onSimCheckResponse(final PinResult result) {
- post(new Runnable() {
- @Override
- public void run() {
- mRemainingAttempts = result.getAttemptsRemaining();
- if (mSimUnlockProgressDialog != null) {
- mSimUnlockProgressDialog.hide();
- }
- resetPasswordText(true /* animate */,
- /* announce */
- result.getType() != PinResult.PIN_RESULT_TYPE_SUCCESS);
- if (result.getType() == PinResult.PIN_RESULT_TYPE_SUCCESS) {
- Dependency.get(KeyguardUpdateMonitor.class)
- .reportSimUnlocked(mSubId);
- mRemainingAttempts = -1;
- mShowDefaultMessage = true;
- if (mCallback != null) {
- mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
- }
- } else {
- mShowDefaultMessage = false;
- if (result.getType() == PinResult.PIN_RESULT_TYPE_INCORRECT) {
- if (result.getAttemptsRemaining() <= 2) {
- // this is getting critical - show dialog
- getSimRemainingAttemptsDialog(
- result.getAttemptsRemaining()).show();
- } else {
- // show message
- mSecurityMessageDisplay.setMessage(
- getPinPasswordErrorMessage(
- result.getAttemptsRemaining(), false));
- }
- } else {
- // "PIN operation failed!" - no idea what this was and no way to
- // find out. :/
- mSecurityMessageDisplay.setMessage(getContext().getString(
- R.string.kg_password_pin_failed));
- }
- if (DEBUG) Log.d(LOG_TAG, "verifyPasswordAndUnlock "
- + " CheckSimPin.onSimCheckResponse: " + result
- + " attemptsRemaining=" + result.getAttemptsRemaining());
- }
- mCallback.userActivity();
- mCheckSimPinThread = null;
- }
- });
- }
- };
- mCheckSimPinThread.start();
- }
}
@Override
@@ -389,11 +74,6 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView {
}
@Override
- public boolean startDisappearAnimation(Runnable finishRunnable) {
- return false;
- }
-
- @Override
public CharSequence getTitle() {
return getContext().getString(
com.android.internal.R.string.keyguard_accessibility_sim_pin_unlock);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
new file mode 100644
index 000000000000..cc8bf4f2d028
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.keyguard;
+
+import android.annotation.NonNull;
+import android.app.AlertDialog;
+import android.app.AlertDialog.Builder;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.telephony.PinResult;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.ImageView;
+
+import com.android.internal.util.LatencyTracker;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.R;
+
+public class KeyguardSimPinViewController
+ extends KeyguardPinBasedInputViewController<KeyguardSimPinView> {
+ public static final String TAG = "KeyguardSimPinView";
+ private static final String LOG_TAG = "KeyguardSimPinView";
+ private static final boolean DEBUG = KeyguardConstants.DEBUG_SIM_STATES;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final TelephonyManager mTelephonyManager;
+
+ private ProgressDialog mSimUnlockProgressDialog;
+ private CheckSimPin mCheckSimPinThread;
+ private int mRemainingAttempts;
+ // Below flag is set to true during power-up or when a new SIM card inserted on device.
+ // When this is true and when SIM card is PIN locked state, on PIN lock screen, message would
+ // be displayed to inform user about the number of remaining PIN attempts left.
+ private boolean mShowDefaultMessage;
+ private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ private AlertDialog mRemainingAttemptsDialog;
+ private ImageView mSimImageView;
+
+ KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onSimStateChanged(int subId, int slotId, int simState) {
+ if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")");
+ if (simState == TelephonyManager.SIM_STATE_READY) {
+ mRemainingAttempts = -1;
+ resetState();
+ } else {
+ resetState();
+ }
+ }
+ };
+
+ protected KeyguardSimPinViewController(KeyguardSimPinView view,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ SecurityMode securityMode, LockPatternUtils lockPatternUtils,
+ KeyguardSecurityCallback keyguardSecurityCallback,
+ KeyguardMessageAreaController.Factory messageAreaControllerFactory,
+ LatencyTracker latencyTracker,
+ LiftToActivateListener liftToActivateListener,
+ TelephonyManager telephonyManager) {
+ super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
+ messageAreaControllerFactory, latencyTracker, liftToActivateListener);
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mTelephonyManager = telephonyManager;
+ mSimImageView = mView.findViewById(R.id.keyguard_sim);
+ }
+
+ @Override
+ protected void onViewAttached() {
+ super.onViewAttached();
+ }
+
+ @Override
+ void resetState() {
+ super.resetState();
+ if (DEBUG) Log.v(TAG, "Resetting state");
+ handleSubInfoChangeIfNeeded();
+ mMessageAreaController.setMessage("");
+ if (mShowDefaultMessage) {
+ showDefaultMessage();
+ }
+
+ mView.setEsimLocked(KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId));
+ }
+
+ @Override
+ public boolean startDisappearAnimation(Runnable finishRunnable) {
+ return false;
+ }
+
+ @Override
+ public void onResume(int reason) {
+ super.onResume(reason);
+ mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
+ mView.resetState();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback);
+
+ // dismiss the dialog.
+ if (mSimUnlockProgressDialog != null) {
+ mSimUnlockProgressDialog.dismiss();
+ mSimUnlockProgressDialog = null;
+ }
+ }
+
+ @Override
+ protected void verifyPasswordAndUnlock() {
+ String entry = mPasswordEntry.getText();
+
+ if (entry.length() < 4) {
+ // otherwise, display a message to the user, and don't submit.
+ mMessageAreaController.setMessage(
+ com.android.systemui.R.string.kg_invalid_sim_pin_hint);
+ mView.resetPasswordText(true /* animate */, true /* announce */);
+ getKeyguardSecurityCallback().userActivity();
+ return;
+ }
+
+ getSimUnlockProgressDialog().show();
+
+ if (mCheckSimPinThread == null) {
+ mCheckSimPinThread = new CheckSimPin(mPasswordEntry.getText(), mSubId) {
+ @Override
+ void onSimCheckResponse(final PinResult result) {
+ mView.post(() -> {
+ mRemainingAttempts = result.getAttemptsRemaining();
+ if (mSimUnlockProgressDialog != null) {
+ mSimUnlockProgressDialog.hide();
+ }
+ mView.resetPasswordText(true /* animate */,
+ /* announce */
+ result.getType() != PinResult.PIN_RESULT_TYPE_SUCCESS);
+ if (result.getType() == PinResult.PIN_RESULT_TYPE_SUCCESS) {
+ mKeyguardUpdateMonitor.reportSimUnlocked(mSubId);
+ mRemainingAttempts = -1;
+ mShowDefaultMessage = true;
+ getKeyguardSecurityCallback().dismiss(
+ true, KeyguardUpdateMonitor.getCurrentUser());
+ } else {
+ mShowDefaultMessage = false;
+ if (result.getType() == PinResult.PIN_RESULT_TYPE_INCORRECT) {
+ if (result.getAttemptsRemaining() <= 2) {
+ // this is getting critical - show dialog
+ getSimRemainingAttemptsDialog(
+ result.getAttemptsRemaining()).show();
+ } else {
+ // show message
+ mMessageAreaController.setMessage(
+ getPinPasswordErrorMessage(
+ result.getAttemptsRemaining(), false));
+ }
+ } else {
+ // "PIN operation failed!" - no idea what this was and no way to
+ // find out. :/
+ mMessageAreaController.setMessage(mView.getResources().getString(
+ R.string.kg_password_pin_failed));
+ }
+ if (DEBUG) {
+ Log.d(LOG_TAG, "verifyPasswordAndUnlock "
+ + " CheckSimPin.onSimCheckResponse: " + result
+ + " attemptsRemaining=" + result.getAttemptsRemaining());
+ }
+ }
+ getKeyguardSecurityCallback().userActivity();
+ mCheckSimPinThread = null;
+ });
+ }
+ };
+ mCheckSimPinThread.start();
+ }
+ }
+
+ private Dialog getSimUnlockProgressDialog() {
+ if (mSimUnlockProgressDialog == null) {
+ mSimUnlockProgressDialog = new ProgressDialog(mView.getContext());
+ mSimUnlockProgressDialog.setMessage(
+ mView.getResources().getString(R.string.kg_sim_unlock_progress_dialog_message));
+ mSimUnlockProgressDialog.setIndeterminate(true);
+ mSimUnlockProgressDialog.setCancelable(false);
+ mSimUnlockProgressDialog.getWindow().setType(
+ WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ }
+ return mSimUnlockProgressDialog;
+ }
+
+
+ private Dialog getSimRemainingAttemptsDialog(int remaining) {
+ String msg = getPinPasswordErrorMessage(remaining, false);
+ if (mRemainingAttemptsDialog == null) {
+ Builder builder = new AlertDialog.Builder(mView.getContext());
+ builder.setMessage(msg);
+ builder.setCancelable(false);
+ builder.setNeutralButton(R.string.ok, null);
+ mRemainingAttemptsDialog = builder.create();
+ mRemainingAttemptsDialog.getWindow().setType(
+ WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ } else {
+ mRemainingAttemptsDialog.setMessage(msg);
+ }
+ return mRemainingAttemptsDialog;
+ }
+
+
+ private String getPinPasswordErrorMessage(int attemptsRemaining, boolean isDefault) {
+ String displayMessage;
+ int msgId;
+ if (attemptsRemaining == 0) {
+ displayMessage = mView.getResources().getString(
+ R.string.kg_password_wrong_pin_code_pukked);
+ } else if (attemptsRemaining > 0) {
+ msgId = isDefault ? R.plurals.kg_password_default_pin_message :
+ R.plurals.kg_password_wrong_pin_code;
+ displayMessage = mView.getResources()
+ .getQuantityString(msgId, attemptsRemaining, attemptsRemaining);
+ } else {
+ msgId = isDefault ? R.string.kg_sim_pin_instructions : R.string.kg_password_pin_failed;
+ displayMessage = mView.getResources().getString(msgId);
+ }
+ if (KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId)) {
+ displayMessage = mView.getResources()
+ .getString(R.string.kg_sim_lock_esim_instructions, displayMessage);
+ }
+ if (DEBUG) {
+ Log.d(LOG_TAG, "getPinPasswordErrorMessage: attemptsRemaining="
+ + attemptsRemaining + " displayMessage=" + displayMessage);
+ }
+ return displayMessage;
+ }
+
+ private void showDefaultMessage() {
+ setLockedSimMessage();
+ if (mRemainingAttempts >= 0) {
+ return;
+ }
+
+ // Sending empty PIN here to query the number of remaining PIN attempts
+ new CheckSimPin("", mSubId) {
+ void onSimCheckResponse(final PinResult result) {
+ Log.d(LOG_TAG, "onSimCheckResponse " + " empty One result "
+ + result.toString());
+ if (result.getAttemptsRemaining() >= 0) {
+ mRemainingAttempts = result.getAttemptsRemaining();
+ setLockedSimMessage();
+ }
+ }
+ }.start();
+ }
+
+ /**
+ * Since the IPC can block, we want to run the request in a separate thread
+ * with a callback.
+ */
+ private abstract class CheckSimPin extends Thread {
+ private final String mPin;
+ private int mSubId;
+
+ protected CheckSimPin(String pin, int subId) {
+ mPin = pin;
+ mSubId = subId;
+ }
+
+ abstract void onSimCheckResponse(@NonNull PinResult result);
+
+ @Override
+ public void run() {
+ if (DEBUG) {
+ Log.v(TAG, "call supplyPinReportResultForSubscriber(subid=" + mSubId + ")");
+ }
+ TelephonyManager telephonyManager =
+ mTelephonyManager.createForSubscriptionId(mSubId);
+ final PinResult result = telephonyManager.supplyPinReportPinResult(mPin);
+ if (result == null) {
+ Log.e(TAG, "Error result for supplyPinReportResult.");
+ mView.post(() -> onSimCheckResponse(PinResult.getDefaultFailedResult()));
+ } else {
+ if (DEBUG) {
+ Log.v(TAG, "supplyPinReportResult returned: " + result.toString());
+ }
+ mView.post(() -> onSimCheckResponse(result));
+ }
+ }
+ }
+
+ private void setLockedSimMessage() {
+ boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId);
+ int count = 1;
+ if (mTelephonyManager != null) {
+ count = mTelephonyManager.getActiveModemCount();
+ }
+ Resources rez = mView.getResources();
+ String msg;
+ TypedArray array = mView.getContext().obtainStyledAttributes(
+ new int[] { R.attr.wallpaperTextColor });
+ int color = array.getColor(0, Color.WHITE);
+ array.recycle();
+ if (count < 2) {
+ msg = rez.getString(R.string.kg_sim_pin_instructions);
+ } else {
+ SubscriptionInfo info = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(mSubId);
+ CharSequence displayName = info != null ? info.getDisplayName() : ""; // don't crash
+ msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName);
+ if (info != null) {
+ color = info.getIconTint();
+ }
+ }
+ if (isEsimLocked) {
+ msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg);
+ }
+
+ if (mView.getVisibility() == View.VISIBLE) {
+ mMessageAreaController.setMessage(msg);
+ }
+ mSimImageView.setImageTintList(ColorStateList.valueOf(color));
+ }
+
+ private void handleSubInfoChangeIfNeeded() {
+ int subId = mKeyguardUpdateMonitor
+ .getNextSubIdForState(TelephonyManager.SIM_STATE_PIN_REQUIRED);
+ if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) {
+ mSubId = subId;
+ mShowDefaultMessage = true;
+ mRemainingAttempts = -1;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
index 5148dd709026..0d72c93e9041 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
@@ -16,27 +16,10 @@
package com.android.keyguard;
-import android.annotation.NonNull;
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.ProgressDialog;
import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Color;
-import android.telephony.PinResult;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
import android.util.AttributeSet;
import android.util.Log;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.ImageView;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
@@ -44,48 +27,9 @@ import com.android.systemui.R;
* Displays a PIN pad for entering a PUK (Pin Unlock Kode) provided by a carrier.
*/
public class KeyguardSimPukView extends KeyguardPinBasedInputView {
- private static final String LOG_TAG = "KeyguardSimPukView";
private static final boolean DEBUG = KeyguardConstants.DEBUG;
public static final String TAG = "KeyguardSimPukView";
- private ProgressDialog mSimUnlockProgressDialog = null;
- private CheckSimPuk mCheckSimPukThread;
-
- // Below flag is set to true during power-up or when a new SIM card inserted on device.
- // When this is true and when SIM card is PUK locked state, on PIN lock screen, message would
- // be displayed to inform user about the number of remaining PUK attempts left.
- private boolean mShowDefaultMessage = true;
- private int mRemainingAttempts = -1;
- private String mPukText;
- private String mPinText;
- private StateMachine mStateMachine = new StateMachine();
- private AlertDialog mRemainingAttemptsDialog;
- private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
- private ImageView mSimImageView;
-
- KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() {
- @Override
- public void onSimStateChanged(int subId, int slotId, int simState) {
- if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")");
- switch(simState) {
- // If the SIM is unlocked via a key sequence through the emergency dialer, it will
- // move into the READY state and the PUK lock keyguard should be removed.
- case TelephonyManager.SIM_STATE_READY: {
- mRemainingAttempts = -1;
- mShowDefaultMessage = true;
- // mCallback can be null if onSimStateChanged callback is called when keyguard
- // isn't active.
- if (mCallback != null) {
- mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
- }
- break;
- }
- default:
- resetState();
- }
- }
- };
-
public KeyguardSimPukView(Context context) {
this(context, null);
}
@@ -94,136 +38,14 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView {
super(context, attrs);
}
- private class StateMachine {
- final int ENTER_PUK = 0;
- final int ENTER_PIN = 1;
- final int CONFIRM_PIN = 2;
- final int DONE = 3;
- private int state = ENTER_PUK;
-
- public void next() {
- int msg = 0;
- if (state == ENTER_PUK) {
- if (checkPuk()) {
- state = ENTER_PIN;
- msg = R.string.kg_puk_enter_pin_hint;
- } else {
- msg = R.string.kg_invalid_sim_puk_hint;
- }
- } else if (state == ENTER_PIN) {
- if (checkPin()) {
- state = CONFIRM_PIN;
- msg = R.string.kg_enter_confirm_pin_hint;
- } else {
- msg = R.string.kg_invalid_sim_pin_hint;
- }
- } else if (state == CONFIRM_PIN) {
- if (confirmPin()) {
- state = DONE;
- msg = R.string.keyguard_sim_unlock_progress_dialog_message;
- updateSim();
- } else {
- state = ENTER_PIN; // try again?
- msg = R.string.kg_invalid_confirm_pin_hint;
- }
- }
- resetPasswordText(true /* animate */, true /* announce */);
- if (msg != 0) {
- mSecurityMessageDisplay.setMessage(msg);
- }
- }
-
-
- void reset() {
- mPinText="";
- mPukText="";
- state = ENTER_PUK;
- handleSubInfoChangeIfNeeded();
- if (mShowDefaultMessage) {
- showDefaultMessage();
- }
- boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId);
-
- KeyguardEsimArea esimButton = findViewById(R.id.keyguard_esim_area);
- esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE);
- mPasswordEntry.requestFocus();
- }
-
-
- }
-
- private void showDefaultMessage() {
- if (mRemainingAttempts >= 0) {
- mSecurityMessageDisplay.setMessage(getPukPasswordErrorMessage(
- mRemainingAttempts, true));
- return;
- }
-
- boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId);
- int count = 1;
- TelephonyManager telephonyManager =
- (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
- if (telephonyManager != null) {
- count = telephonyManager.getActiveModemCount();
- }
- Resources rez = getResources();
- String msg;
- TypedArray array = mContext.obtainStyledAttributes(new int[] { R.attr.wallpaperTextColor });
- int color = array.getColor(0, Color.WHITE);
- array.recycle();
- if (count < 2) {
- msg = rez.getString(R.string.kg_puk_enter_puk_hint);
- } else {
- SubscriptionInfo info = Dependency.get(KeyguardUpdateMonitor.class)
- .getSubscriptionInfoForSubId(mSubId);
- CharSequence displayName = info != null ? info.getDisplayName() : "";
- msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName);
- if (info != null) {
- color = info.getIconTint();
- }
- }
- if (isEsimLocked) {
- msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg);
- }
- if (mSecurityMessageDisplay != null) {
- mSecurityMessageDisplay.setMessage(msg);
- }
- mSimImageView.setImageTintList(ColorStateList.valueOf(color));
-
- // Sending empty PUK here to query the number of remaining PIN attempts
- new CheckSimPuk("", "", mSubId) {
- void onSimLockChangedResponse(final PinResult result) {
- if (result == null) Log.e(LOG_TAG, "onSimCheckResponse, pin result is NULL");
- else {
- Log.d(LOG_TAG, "onSimCheckResponse " + " empty One result "
- + result.toString());
- if (result.getAttemptsRemaining() >= 0) {
- mRemainingAttempts = result.getAttemptsRemaining();
- mSecurityMessageDisplay.setMessage(
- getPukPasswordErrorMessage(result.getAttemptsRemaining(), true));
- }
- }
- }
- }.start();
- }
-
- private void handleSubInfoChangeIfNeeded() {
- KeyguardUpdateMonitor monitor = Dependency.get(KeyguardUpdateMonitor.class);
- int subId = monitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PUK_REQUIRED);
- if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) {
- mSubId = subId;
- mShowDefaultMessage = true;
- mRemainingAttempts = -1;
- }
- }
-
@Override
protected int getPromptReasonStringRes(int reason) {
// No message on SIM Puk
return 0;
}
- private String getPukPasswordErrorMessage(int attemptsRemaining, boolean isDefault) {
+ String getPukPasswordErrorMessage(
+ int attemptsRemaining, boolean isDefault, boolean isEsimLocked) {
String displayMessage;
if (attemptsRemaining == 0) {
@@ -238,28 +60,19 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView {
R.string.kg_password_puk_failed;
displayMessage = getContext().getString(msgId);
}
- if (KeyguardEsimArea.isEsimLocked(mContext, mSubId)) {
+ if (isEsimLocked) {
displayMessage = getResources()
.getString(R.string.kg_sim_lock_esim_instructions, displayMessage);
}
- if (DEBUG) Log.d(LOG_TAG, "getPukPasswordErrorMessage:"
- + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage);
+ if (DEBUG) {
+ Log.d(TAG, "getPukPasswordErrorMessage:"
+ + " attemptsRemaining=" + attemptsRemaining
+ + " displayMessage=" + displayMessage);
+ }
return displayMessage;
}
@Override
- public void resetState() {
- super.resetState();
- mStateMachine.reset();
- }
-
- @Override
- protected boolean shouldLockout(long deadline) {
- // SIM PUK doesn't have a timed lockout
- return false;
- }
-
- @Override
protected int getPasswordTextViewId() {
return R.id.pukEntry;
}
@@ -271,197 +84,6 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView {
if (mEcaView instanceof EmergencyCarrierArea) {
((EmergencyCarrierArea) mEcaView).setCarrierTextVisible(true);
}
- mSimImageView = findViewById(R.id.keyguard_sim);
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mUpdateMonitorCallback);
- resetState();
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mUpdateMonitorCallback);
- }
-
- @Override
- public void showUsabilityHint() {
- }
-
- @Override
- public void onPause() {
- // dismiss the dialog.
- if (mSimUnlockProgressDialog != null) {
- mSimUnlockProgressDialog.dismiss();
- mSimUnlockProgressDialog = null;
- }
- }
-
- /**
- * Since the IPC can block, we want to run the request in a separate thread
- * with a callback.
- */
- private abstract class CheckSimPuk extends Thread {
-
- private final String mPin, mPuk;
- private final int mSubId;
-
- protected CheckSimPuk(String puk, String pin, int subId) {
- mPuk = puk;
- mPin = pin;
- mSubId = subId;
- }
-
- abstract void onSimLockChangedResponse(@NonNull PinResult result);
-
- @Override
- public void run() {
- if (DEBUG) Log.v(TAG, "call supplyPukReportResult()");
- TelephonyManager telephonyManager =
- ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE))
- .createForSubscriptionId(mSubId);
- final PinResult result = telephonyManager.supplyPukReportPinResult(mPuk, mPin);
- if (result == null) {
- Log.e(TAG, "Error result for supplyPukReportResult.");
- post(new Runnable() {
- @Override
- public void run() {
- onSimLockChangedResponse(PinResult.getDefaultFailedResult());
- }
- });
- } else {
- if (DEBUG) {
- Log.v(TAG, "supplyPukReportResult returned: " + result.toString());
- }
- post(new Runnable() {
- @Override
- public void run() {
- onSimLockChangedResponse(result);
- }
- });
- }
- }
- }
-
- private Dialog getSimUnlockProgressDialog() {
- if (mSimUnlockProgressDialog == null) {
- mSimUnlockProgressDialog = new ProgressDialog(mContext);
- mSimUnlockProgressDialog.setMessage(
- mContext.getString(R.string.kg_sim_unlock_progress_dialog_message));
- mSimUnlockProgressDialog.setIndeterminate(true);
- mSimUnlockProgressDialog.setCancelable(false);
- if (!(mContext instanceof Activity)) {
- mSimUnlockProgressDialog.getWindow().setType(
- WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
- }
- }
- return mSimUnlockProgressDialog;
- }
-
- private Dialog getPukRemainingAttemptsDialog(int remaining) {
- String msg = getPukPasswordErrorMessage(remaining, false);
- if (mRemainingAttemptsDialog == null) {
- AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
- builder.setMessage(msg);
- builder.setCancelable(false);
- builder.setNeutralButton(R.string.ok, null);
- mRemainingAttemptsDialog = builder.create();
- mRemainingAttemptsDialog.getWindow().setType(
- WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
- } else {
- mRemainingAttemptsDialog.setMessage(msg);
- }
- return mRemainingAttemptsDialog;
- }
-
- private boolean checkPuk() {
- // make sure the puk is at least 8 digits long.
- if (mPasswordEntry.getText().length() == 8) {
- mPukText = mPasswordEntry.getText();
- return true;
- }
- return false;
- }
-
- private boolean checkPin() {
- // make sure the PIN is between 4 and 8 digits
- int length = mPasswordEntry.getText().length();
- if (length >= 4 && length <= 8) {
- mPinText = mPasswordEntry.getText();
- return true;
- }
- return false;
- }
-
- public boolean confirmPin() {
- return mPinText.equals(mPasswordEntry.getText());
- }
-
- private void updateSim() {
- getSimUnlockProgressDialog().show();
-
- if (mCheckSimPukThread == null) {
- mCheckSimPukThread = new CheckSimPuk(mPukText, mPinText, mSubId) {
- @Override
- void onSimLockChangedResponse(final PinResult result) {
- post(new Runnable() {
- @Override
- public void run() {
- if (mSimUnlockProgressDialog != null) {
- mSimUnlockProgressDialog.hide();
- }
- resetPasswordText(true /* animate */,
- /* announce */
- result.getType() != PinResult.PIN_RESULT_TYPE_SUCCESS);
- if (result.getType() == PinResult.PIN_RESULT_TYPE_SUCCESS) {
- Dependency.get(KeyguardUpdateMonitor.class)
- .reportSimUnlocked(mSubId);
- mRemainingAttempts = -1;
- mShowDefaultMessage = true;
- if (mCallback != null) {
- mCallback.dismiss(true,
- KeyguardUpdateMonitor.getCurrentUser());
- }
- } else {
- mShowDefaultMessage = false;
- if (result.getType() == PinResult.PIN_RESULT_TYPE_INCORRECT) {
- // show message
- mSecurityMessageDisplay.setMessage(getPukPasswordErrorMessage(
- result.getAttemptsRemaining(), false));
- if (result.getAttemptsRemaining() <= 2) {
- // this is getting critical - show dialog
- getPukRemainingAttemptsDialog(
- result.getAttemptsRemaining()).show();
- } else {
- // show message
- mSecurityMessageDisplay.setMessage(
- getPukPasswordErrorMessage(
- result.getAttemptsRemaining(), false));
- }
- } else {
- mSecurityMessageDisplay.setMessage(getContext().getString(
- R.string.kg_password_puk_failed));
- }
- if (DEBUG) Log.d(LOG_TAG, "verifyPasswordAndUnlock "
- + " UpdateSim.onSimCheckResponse: "
- + " attemptsRemaining=" + result.getAttemptsRemaining());
- }
- mStateMachine.reset();
- mCheckSimPukThread = null;
- }
- });
- }
- };
- mCheckSimPukThread.start();
- }
- }
-
- @Override
- protected void verifyPasswordAndUnlock() {
- mStateMachine.next();
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
new file mode 100644
index 000000000000..a87374939ba6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -0,0 +1,413 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.keyguard;
+
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.telephony.PinResult;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.ImageView;
+
+import com.android.internal.util.LatencyTracker;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+
+public class KeyguardSimPukViewController
+ extends KeyguardPinBasedInputViewController<KeyguardSimPukView> {
+ private static final boolean DEBUG = KeyguardConstants.DEBUG;
+ public static final String TAG = "KeyguardSimPukView";
+
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final TelephonyManager mTelephonyManager;
+
+ private String mPukText;
+ private String mPinText;
+ private int mRemainingAttempts;
+ // Below flag is set to true during power-up or when a new SIM card inserted on device.
+ // When this is true and when SIM card is PUK locked state, on PIN lock screen, message would
+ // be displayed to inform user about the number of remaining PUK attempts left.
+ private boolean mShowDefaultMessage;
+ private StateMachine mStateMachine = new StateMachine();
+ private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ private CheckSimPuk mCheckSimPukThread;
+ private ProgressDialog mSimUnlockProgressDialog;
+
+ KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onSimStateChanged(int subId, int slotId, int simState) {
+ if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")");
+ // If the SIM is unlocked via a key sequence through the emergency dialer, it will
+ // move into the READY state and the PUK lock keyguard should be removed.
+ if (simState == TelephonyManager.SIM_STATE_READY) {
+ mRemainingAttempts = -1;
+ mShowDefaultMessage = true;
+ getKeyguardSecurityCallback().dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
+ } else {
+ resetState();
+ }
+ }
+ };
+ private ImageView mSimImageView;
+ private AlertDialog mRemainingAttemptsDialog;
+
+ protected KeyguardSimPukViewController(KeyguardSimPukView view,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ SecurityMode securityMode, LockPatternUtils lockPatternUtils,
+ KeyguardSecurityCallback keyguardSecurityCallback,
+ KeyguardMessageAreaController.Factory messageAreaControllerFactory,
+ LatencyTracker latencyTracker,
+ LiftToActivateListener liftToActivateListener,
+ TelephonyManager telephonyManager) {
+ super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
+ messageAreaControllerFactory, latencyTracker, liftToActivateListener);
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mTelephonyManager = telephonyManager;
+ mSimImageView = mView.findViewById(R.id.keyguard_sim);
+ }
+
+ @Override
+ protected void onViewAttached() {
+ super.onViewAttached();
+ mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
+ }
+
+ @Override
+ protected void onViewDetached() {
+ super.onViewDetached();
+ mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback);
+ }
+
+ @Override
+ void resetState() {
+ super.resetState();
+ mStateMachine.reset();
+ }
+
+ @Override
+ protected void verifyPasswordAndUnlock() {
+ mStateMachine.next();
+ }
+
+ private class StateMachine {
+ static final int ENTER_PUK = 0;
+ static final int ENTER_PIN = 1;
+ static final int CONFIRM_PIN = 2;
+ static final int DONE = 3;
+
+ private int mState = ENTER_PUK;
+
+ public void next() {
+ int msg = 0;
+ if (mState == ENTER_PUK) {
+ if (checkPuk()) {
+ mState = ENTER_PIN;
+ msg = com.android.systemui.R.string.kg_puk_enter_pin_hint;
+ } else {
+ msg = com.android.systemui.R.string.kg_invalid_sim_puk_hint;
+ }
+ } else if (mState == ENTER_PIN) {
+ if (checkPin()) {
+ mState = CONFIRM_PIN;
+ msg = com.android.systemui.R.string.kg_enter_confirm_pin_hint;
+ } else {
+ msg = com.android.systemui.R.string.kg_invalid_sim_pin_hint;
+ }
+ } else if (mState == CONFIRM_PIN) {
+ if (confirmPin()) {
+ mState = DONE;
+ msg = com.android.systemui.R.string.keyguard_sim_unlock_progress_dialog_message;
+ updateSim();
+ } else {
+ mState = ENTER_PIN; // try again?
+ msg = com.android.systemui.R.string.kg_invalid_confirm_pin_hint;
+ }
+ }
+ mView.resetPasswordText(true /* animate */, true /* announce */);
+ if (msg != 0) {
+ mMessageAreaController.setMessage(msg);
+ }
+ }
+
+
+ void reset() {
+ mPinText = "";
+ mPukText = "";
+ mState = ENTER_PUK;
+ handleSubInfoChangeIfNeeded();
+ if (mShowDefaultMessage) {
+ showDefaultMessage();
+ }
+ boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId);
+
+ KeyguardEsimArea esimButton = mView.findViewById(R.id.keyguard_esim_area);
+ esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE);
+ mPasswordEntry.requestFocus();
+ }
+ }
+
+ private void showDefaultMessage() {
+ if (mRemainingAttempts >= 0) {
+ mMessageAreaController.setMessage(mView.getPukPasswordErrorMessage(
+ mRemainingAttempts, true,
+ KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId)));
+ return;
+ }
+
+ boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId);
+ int count = 1;
+ if (mTelephonyManager != null) {
+ count = mTelephonyManager.getActiveModemCount();
+ }
+ Resources rez = mView.getResources();
+ String msg;
+ TypedArray array = mView.getContext().obtainStyledAttributes(
+ new int[] { R.attr.wallpaperTextColor });
+ int color = array.getColor(0, Color.WHITE);
+ array.recycle();
+ if (count < 2) {
+ msg = rez.getString(R.string.kg_puk_enter_puk_hint);
+ } else {
+ SubscriptionInfo info = Dependency.get(KeyguardUpdateMonitor.class)
+ .getSubscriptionInfoForSubId(mSubId);
+ CharSequence displayName = info != null ? info.getDisplayName() : "";
+ msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName);
+ if (info != null) {
+ color = info.getIconTint();
+ }
+ }
+ if (isEsimLocked) {
+ msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg);
+ }
+ mMessageAreaController.setMessage(msg);
+ mSimImageView.setImageTintList(ColorStateList.valueOf(color));
+
+ // Sending empty PUK here to query the number of remaining PIN attempts
+ new CheckSimPuk("", "", mSubId) {
+ void onSimLockChangedResponse(final PinResult result) {
+ if (result == null) Log.e(TAG, "onSimCheckResponse, pin result is NULL");
+ else {
+ Log.d(TAG, "onSimCheckResponse " + " empty One result "
+ + result.toString());
+ if (result.getAttemptsRemaining() >= 0) {
+ mRemainingAttempts = result.getAttemptsRemaining();
+ mMessageAreaController.setMessage(
+ mView.getPukPasswordErrorMessage(
+ result.getAttemptsRemaining(), true,
+ KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId)));
+ }
+ }
+ }
+ }.start();
+ }
+
+ private boolean checkPuk() {
+ // make sure the puk is at least 8 digits long.
+ if (mPasswordEntry.getText().length() == 8) {
+ mPukText = mPasswordEntry.getText();
+ return true;
+ }
+ return false;
+ }
+
+ private boolean checkPin() {
+ // make sure the PIN is between 4 and 8 digits
+ int length = mPasswordEntry.getText().length();
+ if (length >= 4 && length <= 8) {
+ mPinText = mPasswordEntry.getText();
+ return true;
+ }
+ return false;
+ }
+
+ public boolean confirmPin() {
+ return mPinText.equals(mPasswordEntry.getText());
+ }
+
+
+
+
+ private void updateSim() {
+ getSimUnlockProgressDialog().show();
+
+ if (mCheckSimPukThread == null) {
+ mCheckSimPukThread = new CheckSimPuk(mPukText, mPinText, mSubId) {
+ @Override
+ void onSimLockChangedResponse(final PinResult result) {
+ mView.post(() -> {
+ if (mSimUnlockProgressDialog != null) {
+ mSimUnlockProgressDialog.hide();
+ }
+ mView.resetPasswordText(true /* animate */,
+ /* announce */
+ result.getType() != PinResult.PIN_RESULT_TYPE_SUCCESS);
+ if (result.getType() == PinResult.PIN_RESULT_TYPE_SUCCESS) {
+ mKeyguardUpdateMonitor.reportSimUnlocked(mSubId);
+ mRemainingAttempts = -1;
+ mShowDefaultMessage = true;
+
+ getKeyguardSecurityCallback().dismiss(
+ true, KeyguardUpdateMonitor.getCurrentUser());
+ } else {
+ mShowDefaultMessage = false;
+ if (result.getType() == PinResult.PIN_RESULT_TYPE_INCORRECT) {
+ // show message
+ mMessageAreaController.setMessage(mView.getPukPasswordErrorMessage(
+ result.getAttemptsRemaining(), false,
+ KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId)));
+ if (result.getAttemptsRemaining() <= 2) {
+ // this is getting critical - show dialog
+ getPukRemainingAttemptsDialog(
+ result.getAttemptsRemaining()).show();
+ } else {
+ // show message
+ mMessageAreaController.setMessage(
+ mView.getPukPasswordErrorMessage(
+ result.getAttemptsRemaining(), false,
+ KeyguardEsimArea.isEsimLocked(
+ mView.getContext(), mSubId)));
+ }
+ } else {
+ mMessageAreaController.setMessage(mView.getResources().getString(
+ R.string.kg_password_puk_failed));
+ }
+ if (DEBUG) {
+ Log.d(TAG, "verifyPasswordAndUnlock "
+ + " UpdateSim.onSimCheckResponse: "
+ + " attemptsRemaining=" + result.getAttemptsRemaining());
+ }
+ }
+ mStateMachine.reset();
+ mCheckSimPukThread = null;
+ });
+ }
+ };
+ mCheckSimPukThread.start();
+ }
+ }
+
+ @Override
+ protected boolean shouldLockout(long deadline) {
+ // SIM PUK doesn't have a timed lockout
+ return false;
+ }
+
+ private Dialog getSimUnlockProgressDialog() {
+ if (mSimUnlockProgressDialog == null) {
+ mSimUnlockProgressDialog = new ProgressDialog(mView.getContext());
+ mSimUnlockProgressDialog.setMessage(
+ mView.getResources().getString(R.string.kg_sim_unlock_progress_dialog_message));
+ mSimUnlockProgressDialog.setIndeterminate(true);
+ mSimUnlockProgressDialog.setCancelable(false);
+ if (!(mView.getContext() instanceof Activity)) {
+ mSimUnlockProgressDialog.getWindow().setType(
+ WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ }
+ }
+ return mSimUnlockProgressDialog;
+ }
+
+ private void handleSubInfoChangeIfNeeded() {
+ int subId = mKeyguardUpdateMonitor.getNextSubIdForState(
+ TelephonyManager.SIM_STATE_PUK_REQUIRED);
+ if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) {
+ mSubId = subId;
+ mShowDefaultMessage = true;
+ mRemainingAttempts = -1;
+ }
+ }
+
+
+ private Dialog getPukRemainingAttemptsDialog(int remaining) {
+ String msg = mView.getPukPasswordErrorMessage(remaining, false,
+ KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId));
+ if (mRemainingAttemptsDialog == null) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(mView.getContext());
+ builder.setMessage(msg);
+ builder.setCancelable(false);
+ builder.setNeutralButton(R.string.ok, null);
+ mRemainingAttemptsDialog = builder.create();
+ mRemainingAttemptsDialog.getWindow().setType(
+ WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ } else {
+ mRemainingAttemptsDialog.setMessage(msg);
+ }
+ return mRemainingAttemptsDialog;
+ }
+
+ @Override
+ public void onPause() {
+ // dismiss the dialog.
+ if (mSimUnlockProgressDialog != null) {
+ mSimUnlockProgressDialog.dismiss();
+ mSimUnlockProgressDialog = null;
+ }
+ }
+
+ /**
+ * Since the IPC can block, we want to run the request in a separate thread
+ * with a callback.
+ */
+ private abstract class CheckSimPuk extends Thread {
+
+ private final String mPin, mPuk;
+ private final int mSubId;
+
+ protected CheckSimPuk(String puk, String pin, int subId) {
+ mPuk = puk;
+ mPin = pin;
+ mSubId = subId;
+ }
+
+ abstract void onSimLockChangedResponse(@NonNull PinResult result);
+
+ @Override
+ public void run() {
+ if (DEBUG) Log.v(TAG, "call supplyPukReportResult()");
+ TelephonyManager telephonyManager = mTelephonyManager.createForSubscriptionId(mSubId);
+ final PinResult result = telephonyManager.supplyPukReportPinResult(mPuk, mPin);
+ if (result == null) {
+ Log.e(TAG, "Error result for supplyPukReportResult.");
+ mView.post(() -> onSimLockChangedResponse(PinResult.getDefaultFailedResult()));
+ } else {
+ if (DEBUG) {
+ Log.v(TAG, "supplyPukReportResult returned: " + result.toString());
+ }
+ mView.post(new Runnable() {
+ @Override
+ public void run() {
+ onSimLockChangedResponse(result);
+ }
+ });
+ }
+ }
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java b/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java
index e59602b1cfff..425e50ed6397 100644
--- a/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java
+++ b/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java
@@ -16,11 +16,12 @@
package com.android.keyguard;
-import android.content.Context;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
+import javax.inject.Inject;
+
/**
* Hover listener that implements lift-to-activate interaction for
* accessibility. May be added to multiple views.
@@ -31,9 +32,9 @@ class LiftToActivateListener implements View.OnHoverListener {
private boolean mCachedClickableState;
- public LiftToActivateListener(Context context) {
- mAccessibilityManager = (AccessibilityManager) context.getSystemService(
- Context.ACCESSIBILITY_SERVICE);
+ @Inject
+ LiftToActivateListener(AccessibilityManager accessibilityManager) {
+ mAccessibilityManager = accessibilityManager;
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index b0457fce6a1a..2205fdd4267d 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -26,6 +26,7 @@ import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityManager;
import android.widget.TextView;
import com.android.internal.widget.LockPatternUtils;
@@ -90,7 +91,8 @@ public class NumPadKey extends ViewGroup {
}
setOnClickListener(mListener);
- setOnHoverListener(new LiftToActivateListener(context));
+ setOnHoverListener(new LiftToActivateListener(
+ (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE)));
mLockPatternUtils = new LockPatternUtils(context);
mPM = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
index b6010c8915e7..881108858b51 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
@@ -22,6 +22,7 @@ import android.view.ViewGroup;
import com.android.keyguard.KeyguardHostView;
import com.android.keyguard.KeyguardMessageArea;
import com.android.keyguard.KeyguardSecurityContainer;
+import com.android.keyguard.KeyguardSecurityViewFlipper;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
@@ -58,7 +59,15 @@ public interface KeyguardBouncerModule {
/** */
@Provides
@KeyguardBouncerScope
- static KeyguardSecurityContainer preovidesKeyguardSecurityContainer(KeyguardHostView hostView) {
+ static KeyguardSecurityContainer providesKeyguardSecurityContainer(KeyguardHostView hostView) {
return hostView.findViewById(R.id.keyguard_security_container);
}
+
+ /** */
+ @Provides
+ @KeyguardBouncerScope
+ static KeyguardSecurityViewFlipper providesKeyguardSecurityViewFlipper(
+ KeyguardSecurityContainer containerView) {
+ return containerView.findViewById(R.id.view_flipper);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index ed78c94d45f9..832edf719dbb 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -80,7 +80,6 @@ import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.DozeParameters;
@@ -280,7 +279,6 @@ public class Dependency {
@Inject Lazy<VisualStabilityManager> mVisualStabilityManager;
@Inject Lazy<NotificationGutsManager> mNotificationGutsManager;
@Inject Lazy<NotificationMediaManager> mNotificationMediaManager;
- @Inject Lazy<NotificationBlockingHelperManager> mNotificationBlockingHelperManager;
@Inject Lazy<NotificationRemoteInputManager> mNotificationRemoteInputManager;
@Inject Lazy<SmartReplyConstants> mSmartReplyConstants;
@Inject Lazy<NotificationListener> mNotificationListener;
@@ -473,8 +471,6 @@ public class Dependency {
mNotificationGroupAlertTransferHelper::get);
mProviders.put(NotificationMediaManager.class, mNotificationMediaManager::get);
mProviders.put(NotificationGutsManager.class, mNotificationGutsManager::get);
- mProviders.put(NotificationBlockingHelperManager.class,
- mNotificationBlockingHelperManager::get);
mProviders.put(NotificationRemoteInputManager.class,
mNotificationRemoteInputManager::get);
mProviders.put(SmartReplyConstants.class, mSmartReplyConstants::get);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index f15949977754..80253b424335 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -17,6 +17,7 @@
package com.android.systemui;
import android.content.Context;
+import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.Handler;
import android.util.Log;
@@ -26,6 +27,7 @@ import com.android.systemui.dagger.DaggerGlobalRootComponent;
import com.android.systemui.dagger.GlobalRootComponent;
import com.android.systemui.dagger.SysUIComponent;
import com.android.systemui.dagger.WMComponent;
+import com.android.systemui.navigationbar.gestural.BackGestureTfClassifierProvider;
import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider;
import java.util.concurrent.ExecutionException;
@@ -110,12 +112,16 @@ public class SystemUIFactory {
return mSysUIComponent;
}
- /** Returns the list of system UI components that should be started. */
+ /**
+ * Returns the list of system UI components that should be started.
+ */
public String[] getSystemUIServiceComponents(Resources resources) {
return resources.getStringArray(R.array.config_systemUIServiceComponents);
}
- /** Returns the list of system UI components that should be started per user. */
+ /**
+ * Returns the list of system UI components that should be started per user.
+ */
public String[] getSystemUIServiceComponentsPerUser(Resources resources) {
return resources.getStringArray(R.array.config_systemUIServiceComponentsPerUser);
}
@@ -125,9 +131,17 @@ public class SystemUIFactory {
* This method is overridden in vendor specific implementation of Sys UI.
*/
public ScreenshotNotificationSmartActionsProvider
- createScreenshotNotificationSmartActionsProvider(Context context,
- Executor executor,
- Handler uiHandler) {
+ createScreenshotNotificationSmartActionsProvider(
+ Context context, Executor executor, Handler uiHandler) {
return new ScreenshotNotificationSmartActionsProvider();
}
-}
+
+ /**
+ * Creates an instance of BackGestureTfClassifierProvider.
+ * This method is overridden in vendor specific implementation of Sys UI.
+ */
+ public BackGestureTfClassifierProvider createBackGestureTfClassifierProvider(
+ AssetManager am) {
+ return new BackGestureTfClassifierProvider();
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 494a0f64cea4..714095631fdb 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -48,6 +48,7 @@ import android.view.View;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.R;
import com.android.systemui.shared.system.WindowManagerWrapper;
@@ -65,7 +66,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
private final Point mDisplaySize = new Point();
private final int mDisplayId;
@Surface.Rotation
- private int mRotation;
+ @VisibleForTesting
+ int mRotation;
private final Rect mMagnificationFrame = new Rect();
private final SurfaceControl.Transaction mTransaction;
@@ -97,6 +99,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
private SurfaceView mMirrorSurfaceView;
private int mMirrorSurfaceMargin;
private int mBorderDragSize;
+ private int mDragViewSize;
private int mOuterBorderSize;
// The boundary of magnification frame.
private final Rect mMagnificationFrameBoundary = new Rect();
@@ -109,7 +112,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
WindowMagnificationController(Context context, @NonNull Handler handler,
SfVsyncFrameCallbackProvider sfVsyncFrameProvider,
- MirrorWindowControl mirrorWindowControl, SurfaceControl.Transaction transaction,
+ MirrorWindowControl mirrorWindowControl, SurfaceControl.Transaction transaction,
@NonNull WindowMagnifierCallback callback) {
mContext = context;
mHandler = handler;
@@ -168,6 +171,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
R.dimen.magnification_mirror_surface_margin);
mBorderDragSize = mResources.getDimensionPixelSize(
R.dimen.magnification_border_drag_size);
+ mDragViewSize = mResources.getDimensionPixelSize(
+ R.dimen.magnification_drag_view_size);
mOuterBorderSize = mResources.getDimensionPixelSize(
R.dimen.magnification_outer_border_margin);
}
@@ -203,13 +208,12 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
* @param configDiff a bit mask of the differences between the configurations
*/
void onConfigurationChanged(int configDiff) {
- if (!isWindowVisible()) {
- return;
- }
if ((configDiff & ActivityInfo.CONFIG_DENSITY) != 0) {
updateDimensions();
- mWm.removeView(mMirrorView);
- createMirrorWindow();
+ if (isWindowVisible()) {
+ mWm.removeView(mMirrorView);
+ createMirrorWindow();
+ }
} else if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) {
onRotate();
}
@@ -217,16 +221,20 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
/** Handles MirrorWindow position when the device rotation changed. */
private void onRotate() {
- Display display = mContext.getDisplay();
+ final Display display = mContext.getDisplay();
+ final int oldRotation = mRotation;
display.getRealSize(mDisplaySize);
setMagnificationFrameBoundary();
+ mRotation = display.getRotation();
+ if (!isWindowVisible()) {
+ return;
+ }
// Keep MirrorWindow position on the screen unchanged when device rotates 90°
// clockwise or anti-clockwise.
- final int rotationDegree = getDegreeFromRotation(display.getRotation(), mRotation);
+ final int rotationDegree = getDegreeFromRotation(mRotation, oldRotation);
final Matrix matrix = new Matrix();
matrix.setRotate(rotationDegree);
- mRotation = display.getRotation();
if (rotationDegree == 90) {
matrix.postTranslate(mDisplaySize.x, 0);
} else if (rotationDegree == 270) {
@@ -307,6 +315,10 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
Region regionInsideDragBorder = new Region(mBorderDragSize, mBorderDragSize,
mMirrorView.getWidth() - mBorderDragSize,
mMirrorView.getHeight() - mBorderDragSize);
+ Rect dragArea = new Rect(mMirrorView.getWidth() - mDragViewSize - mBorderDragSize,
+ mMirrorView.getHeight() - mDragViewSize - mBorderDragSize,
+ mMirrorView.getWidth(), mMirrorView.getHeight());
+ regionInsideDragBorder.op(dragArea, Region.Op.DIFFERENCE);
return regionInsideDragBorder;
}
@@ -555,6 +567,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
/**
* Gets the scale.
+ *
* @return {@link Float#NaN} if the window is invisible.
*/
float getScale() {
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index a3339f6fc051..0fd47654ebb9 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -16,8 +16,13 @@
package com.android.systemui.appops;
+import static android.media.AudioManager.ACTION_MICROPHONE_MUTE_CHANGED;
+
import android.app.AppOpsManager;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.location.LocationManager;
import android.media.AudioManager;
@@ -34,6 +39,7 @@ import androidx.annotation.WorkerThread;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dumpable;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
@@ -54,7 +60,7 @@ import javax.inject.Inject;
* NotificationPresenter to be displayed to the user.
*/
@SysUISingleton
-public class AppOpsControllerImpl implements AppOpsController,
+public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsController,
AppOpsManager.OnOpActiveChangedInternalListener,
AppOpsManager.OnOpNotedListener, Dumpable {
@@ -65,6 +71,7 @@ public class AppOpsControllerImpl implements AppOpsController,
private static final String TAG = "AppOpsControllerImpl";
private static final boolean DEBUG = false;
+ private final BroadcastDispatcher mDispatcher;
private final AppOpsManager mAppOps;
private final AudioManager mAudioManager;
private final LocationManager mLocationManager;
@@ -79,6 +86,7 @@ public class AppOpsControllerImpl implements AppOpsController,
private final SparseArray<Set<Callback>> mCallbacksByCode = new SparseArray<>();
private final PermissionFlagsCache mFlagsCache;
private boolean mListening;
+ private boolean mMicMuted;
@GuardedBy("mActiveItems")
private final List<AppOpItem> mActiveItems = new ArrayList<>();
@@ -105,8 +113,10 @@ public class AppOpsControllerImpl implements AppOpsController,
@Background Looper bgLooper,
DumpManager dumpManager,
PermissionFlagsCache cache,
- AudioManager audioManager
+ AudioManager audioManager,
+ BroadcastDispatcher dispatcher
) {
+ mDispatcher = dispatcher;
mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
mFlagsCache = cache;
mBGHandler = new H(bgLooper);
@@ -115,6 +125,7 @@ public class AppOpsControllerImpl implements AppOpsController,
mCallbacksByCode.put(OPS[i], new ArraySet<>());
}
mAudioManager = audioManager;
+ mMicMuted = audioManager.isMicrophoneMute();
mLocationManager = context.getSystemService(LocationManager.class);
dumpManager.registerDumpable(TAG, this);
}
@@ -133,6 +144,8 @@ public class AppOpsControllerImpl implements AppOpsController,
mAudioManager.registerAudioRecordingCallback(mAudioRecordingCallback, mBGHandler);
mBGHandler.post(() -> mAudioRecordingCallback.onRecordingConfigChanged(
mAudioManager.getActiveRecordingConfigurations()));
+ mDispatcher.registerReceiverWithHandler(this,
+ new IntentFilter(ACTION_MICROPHONE_MUTE_CHANGED), mBGHandler);
} else {
mAppOps.stopWatchingActive(this);
@@ -140,6 +153,7 @@ public class AppOpsControllerImpl implements AppOpsController,
mAudioManager.unregisterAudioRecordingCallback(mAudioRecordingCallback);
mBGHandler.removeCallbacksAndMessages(null); // null removes all
+ mDispatcher.unregisterReceiver(this);
synchronized (mActiveItems) {
mActiveItems.clear();
mRecordingsByUid.clear();
@@ -468,6 +482,9 @@ public class AppOpsControllerImpl implements AppOpsController,
}
private boolean isAnyRecordingPausedLocked(int uid) {
+ if (mMicMuted) {
+ return true;
+ }
List<AudioRecordingConfiguration> configs = mRecordingsByUid.get(uid);
if (configs == null) return false;
int configsNum = configs.size();
@@ -522,6 +539,12 @@ public class AppOpsControllerImpl implements AppOpsController,
}
};
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mMicMuted = mAudioManager.isMicrophoneMute();
+ updateRecordingPausedStatus();
+ }
+
protected class H extends Handler {
H(Looper looper) {
super(looper);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 77afa27b5cca..bde9a6e7c714 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -52,6 +52,7 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.phone.KeyguardBouncer;
import java.util.List;
@@ -64,7 +65,7 @@ import javax.inject.Provider;
*/
@SysUISingleton
public class AuthController extends SystemUI implements CommandQueue.Callbacks,
- AuthDialogCallback, DozeReceiver {
+ AuthDialogCallback, DozeReceiver, KeyguardBouncer.BouncerExpansionCallback {
private static final String TAG = "AuthController";
private static final boolean DEBUG = true;
@@ -426,6 +427,35 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
mCurrentDialog = null;
}
+ /** See {@link KeyguardBouncer.BouncerExpansionCallback#onFullyShown}. */
+ @Override
+ public void onFullyShown() {
+ if (mUdfpsController != null) {
+ mUdfpsController.setBouncerVisibility(true);
+ }
+ }
+
+ /** See {@link KeyguardBouncer.BouncerExpansionCallback#onStartingToHide}. */
+ @Override
+ public void onStartingToHide() {
+ }
+
+ /** See {@link KeyguardBouncer.BouncerExpansionCallback#onStartingToShow}. */
+ @Override
+ public void onStartingToShow() {
+ if (mUdfpsController != null) {
+ mUdfpsController.setBouncerVisibility(true);
+ }
+ }
+
+ /** See {@link KeyguardBouncer.BouncerExpansionCallback#onFullyHidden}. */
+ @Override
+ public void onFullyHidden() {
+ if (mUdfpsController != null) {
+ mUdfpsController.setBouncerVisibility(false);
+ }
+ }
+
private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
mCurrentDialogArgs = args;
final @BiometricAuthenticator.Modality int type = args.argi1;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 06c190f1964c..d79c96ea4774 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -16,17 +16,16 @@
package com.android.systemui.biometrics;
-import android.annotation.NonNull;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
import android.annotation.SuppressLint;
-import android.content.ContentResolver;
import android.content.Context;
+import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.IUdfpsOverlayController;
-import android.os.Handler;
-import android.os.Looper;
import android.os.PowerManager;
import android.os.UserHandle;
import android.provider.Settings;
@@ -38,10 +37,16 @@ import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.WindowManager;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.internal.BrightnessSynchronizer;
import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.settings.SystemSettings;
import java.io.FileWriter;
import java.io.IOException;
@@ -60,8 +65,8 @@ class UdfpsController implements DozeReceiver {
private final FingerprintManager mFingerprintManager;
private final WindowManager mWindowManager;
- private final ContentResolver mContentResolver;
- private final Handler mHandler;
+ private final SystemSettings mSystemSettings;
+ private final DelayableExecutor mFgExecutor;
private final WindowManager.LayoutParams mLayoutParams;
private final UdfpsView mView;
// Debugfs path to control the high-brightness mode.
@@ -81,24 +86,30 @@ class UdfpsController implements DozeReceiver {
// Default non-HBM backlight value normalized to the range [0, 1.0]. Used as a fallback when the
// actual brightness value cannot be retrieved.
private final float mDefaultBrightness;
+ // Indicates whether the overlay is currently showing. Even if it has been requested, it might
+ // not be showing.
private boolean mIsOverlayShowing;
+ // Indicates whether the overlay has been requested.
+ private boolean mIsOverlayRequested;
+ // Indicates whether the bouncer is showing. When it is showing, the overlay needs to be hidden.
+ private boolean mIsBouncerShowing;
// The fingerprint AOD trigger doesn't provide an ACTION_UP/ACTION_CANCEL event to tell us when
// to turn off high brightness mode. To get around this limitation, the state of the AOD
// interrupt is being tracked and a timeout is used as a last resort to turn off high brightness
// mode.
private boolean mIsAodInterruptActive;
- private final Runnable mAodInterruptTimeoutAction = this::onCancelAodInterrupt;
+ @Nullable private Runnable mCancelAodTimeoutAction;
public class UdfpsOverlayController extends IUdfpsOverlayController.Stub {
@Override
public void showUdfpsOverlay() {
- UdfpsController.this.showUdfpsOverlay();
+ UdfpsController.this.setShowOverlay(true);
}
@Override
public void hideUdfpsOverlay() {
- UdfpsController.this.hideUdfpsOverlay();
+ UdfpsController.this.setShowOverlay(false);
}
@Override
@@ -138,18 +149,27 @@ class UdfpsController implements DozeReceiver {
@Inject
UdfpsController(@NonNull Context context,
- @NonNull StatusBarStateController statusBarStateController) {
- mFingerprintManager = context.getSystemService(FingerprintManager.class);
- mWindowManager = context.getSystemService(WindowManager.class);
- mContentResolver = context.getContentResolver();
- mHandler = new Handler(Looper.getMainLooper());
+ @Main Resources resources,
+ LayoutInflater inflater,
+ @Nullable FingerprintManager fingerprintManager,
+ PowerManager powerManager,
+ WindowManager windowManager,
+ SystemSettings systemSettings,
+ @NonNull StatusBarStateController statusBarStateController,
+ @Main DelayableExecutor fgExecutor) {
+ // The fingerprint manager is queried for UDFPS before this class is constructed, so the
+ // fingerprint manager should never be null.
+ mFingerprintManager = checkNotNull(fingerprintManager);
+ mWindowManager = windowManager;
+ mSystemSettings = systemSettings;
+ mFgExecutor = fgExecutor;
mLayoutParams = createLayoutParams(context);
- mView = (UdfpsView) LayoutInflater.from(context).inflate(R.layout.udfps_view, null, false);
+ mView = (UdfpsView) inflater.inflate(R.layout.udfps_view, null, false);
- mHbmPath = context.getResources().getString(R.string.udfps_hbm_sysfs_path);
- mHbmEnableCommand = context.getResources().getString(R.string.udfps_hbm_enable_command);
- mHbmDisableCommand = context.getResources().getString(R.string.udfps_hbm_disable_command);
+ mHbmPath = resources.getString(R.string.udfps_hbm_sysfs_path);
+ mHbmEnableCommand = resources.getString(R.string.udfps_hbm_enable_command);
+ mHbmDisableCommand = resources.getString(R.string.udfps_hbm_disable_command);
mHbmSupported = !TextUtils.isEmpty(mHbmPath);
mView.setHbmSupported(mHbmSupported);
@@ -157,11 +177,11 @@ class UdfpsController implements DozeReceiver {
// This range only consists of the minimum and maximum values, which only cover
// non-high-brightness mode.
- float[] nitsRange = toFloatArray(context.getResources().obtainTypedArray(
+ float[] nitsRange = toFloatArray(resources.obtainTypedArray(
com.android.internal.R.array.config_screenBrightnessNits));
// The last value of this range corresponds to the high-brightness mode.
- float[] nitsAutoBrightnessValues = toFloatArray(context.getResources().obtainTypedArray(
+ float[] nitsAutoBrightnessValues = toFloatArray(resources.obtainTypedArray(
com.android.internal.R.array.config_autoBrightnessDisplayValuesNits));
mHbmNits = nitsAutoBrightnessValues[nitsAutoBrightnessValues.length - 1];
@@ -170,12 +190,12 @@ class UdfpsController implements DozeReceiver {
// This range only consists of the minimum and maximum backlight values, which only apply
// in non-high-brightness mode.
float[] normalizedBacklightRange = normalizeBacklightRange(
- context.getResources().getIntArray(
+ resources.getIntArray(
com.android.internal.R.array.config_screenBrightnessBacklight));
mBacklightToNitsSpline = Spline.createSpline(normalizedBacklightRange, nitsRange);
mNitsToHbmBacklightSpline = Spline.createSpline(hbmNitsRange, normalizedBacklightRange);
- mDefaultBrightness = obtainDefaultBrightness(context);
+ mDefaultBrightness = obtainDefaultBrightness(powerManager);
// TODO(b/160025856): move to the "dump" method.
Log.v(TAG, String.format("ctor | mNitsRange: [%f, %f]", nitsRange[0], nitsRange[1]));
@@ -193,8 +213,37 @@ class UdfpsController implements DozeReceiver {
mView.dozeTimeTick();
}
+ private void setShowOverlay(boolean show) {
+ if (show == mIsOverlayRequested) {
+ return;
+ }
+ mIsOverlayRequested = show;
+ updateOverlay();
+ }
+
+ /**
+ * Call when the visibility of the bouncer changes.
+ *
+ * @param isShowing Whether or not the bouncer is showing
+ */
+ void setBouncerVisibility(boolean isShowing) {
+ if (isShowing == mIsBouncerShowing) {
+ return;
+ }
+ mIsBouncerShowing = isShowing;
+ updateOverlay();
+ }
+
+ private void updateOverlay() {
+ if (mIsOverlayRequested && !mIsBouncerShowing) {
+ showUdfpsOverlay();
+ } else {
+ hideUdfpsOverlay();
+ }
+ }
+
private void showUdfpsOverlay() {
- mHandler.post(() -> {
+ mFgExecutor.execute(() -> {
if (!mIsOverlayShowing) {
try {
Log.v(TAG, "showUdfpsOverlay | adding window");
@@ -211,7 +260,7 @@ class UdfpsController implements DozeReceiver {
}
private void hideUdfpsOverlay() {
- mHandler.post(() -> {
+ mFgExecutor.execute(() -> {
if (mIsOverlayShowing) {
Log.v(TAG, "hideUdfpsOverlay | removing window");
mView.setOnTouchListener(null);
@@ -228,7 +277,7 @@ class UdfpsController implements DozeReceiver {
// Returns a value in the range of [0, 255].
private int computeScrimOpacity() {
// Backlight setting can be NaN, -1.0f, and [0.0f, 1.0f].
- float backlightSetting = Settings.System.getFloatForUser(mContentResolver,
+ float backlightSetting = mSystemSettings.getFloatForUser(
Settings.System.SCREEN_BRIGHTNESS_FLOAT, mDefaultBrightness,
UserHandle.USER_CURRENT);
@@ -265,7 +314,8 @@ class UdfpsController implements DozeReceiver {
// Since the sensor that triggers the AOD interrupt doesn't provide ACTION_UP/ACTION_CANCEL,
// we need to be careful about not letting the screen accidentally remain in high brightness
// mode. As a mitigation, queue a call to cancel the fingerprint scan.
- mHandler.postDelayed(mAodInterruptTimeoutAction, AOD_INTERRUPT_TIMEOUT_MILLIS);
+ mCancelAodTimeoutAction = mFgExecutor.executeDelayed(this::onCancelAodInterrupt,
+ AOD_INTERRUPT_TIMEOUT_MILLIS);
// using a hard-coded value for major and minor until it is available from the sensor
onFingerDown(screenX, screenY, 13.0f, 13.0f);
}
@@ -280,7 +330,10 @@ class UdfpsController implements DozeReceiver {
if (!mIsAodInterruptActive) {
return;
}
- mHandler.removeCallbacks(mAodInterruptTimeoutAction);
+ if (mCancelAodTimeoutAction != null) {
+ mCancelAodTimeoutAction.run();
+ mCancelAodTimeoutAction = null;
+ }
mIsAodInterruptActive = false;
onFingerUp();
}
@@ -338,8 +391,7 @@ class UdfpsController implements DozeReceiver {
return lp;
}
- private static float obtainDefaultBrightness(Context context) {
- PowerManager powerManager = context.getSystemService(PowerManager.class);
+ private static float obtainDefaultBrightness(PowerManager powerManager) {
if (powerManager == null) {
Log.e(TAG, "PowerManager is unavailable. Can't obtain default brightness.");
return 0f;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index f98bb333b6b1..ec60cbd175d0 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -16,15 +16,12 @@
package com.android.systemui.bubbles;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
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.ViewRootImpl.NEW_INSETS_MODE_FULL;
-import static android.view.ViewRootImpl.sNewInsetsMode;
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;
@@ -48,7 +45,6 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
-import android.graphics.Insets;
import android.graphics.Outline;
import android.graphics.Point;
import android.graphics.Rect;
@@ -471,11 +467,7 @@ public class BubbleExpandedView extends LinearLayout {
mKeyboardVisible = false;
mNeedsNewHeight = false;
if (mActivityView != null) {
- if (sNewInsetsMode == NEW_INSETS_MODE_FULL) {
- setImeWindowToDisplay(0, 0);
- } else {
- mActivityView.setForwardedInsets(Insets.of(0, 0, 0, 0));
- }
+ setImeWindowToDisplay(0, 0);
}
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
Log.d(TAG, "onDetachedFromWindow: bubble=" + getBubbleKey());
@@ -525,13 +517,7 @@ public class BubbleExpandedView extends LinearLayout {
insets.getDisplayCutout() != null
? insets.getDisplayCutout().getSafeInsetBottom()
: 0);
- final int insetsBottom = Math.max(activityViewBottom - keyboardTop, 0);
-
- if (sNewInsetsMode == NEW_INSETS_MODE_FULL) {
- setImeWindowToDisplay(getWidth(), insetsBottom);
- } else {
- mActivityView.setForwardedInsets(Insets.of(0, 0, 0, insetsBottom));
- }
+ setImeWindowToDisplay(getWidth(), Math.max(activityViewBottom - keyboardTop, 0));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java
index 2d90c8626a0e..ea612af3d4a4 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java
@@ -33,7 +33,7 @@ public class BubbleLoggerImpl extends UiEventLoggerImpl implements BubbleLogger
* @param e UI event
*/
public void log(Bubble b, UiEventEnum e) {
- super.log(e, b.getUser().getIdentifier(), b.getPackageName());
+ logWithInstanceId(e, b.getAppUid(), b.getPackageName(), b.getInstanceId());
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
index 307362fe790e..25d02a66d56d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
@@ -18,7 +18,7 @@ package com.android.systemui.dagger;
import android.content.BroadcastReceiver;
-import com.android.systemui.media.dialog.MediaOutDialogReceiver;
+import com.android.systemui.media.dialog.MediaOutputDialogReceiver;
import com.android.systemui.screenshot.ActionProxyReceiver;
import com.android.systemui.screenshot.DeleteScreenshotReceiver;
import com.android.systemui.screenshot.SmartActionsReceiver;
@@ -65,8 +65,8 @@ public abstract class DefaultBroadcastReceiverBinder {
*/
@Binds
@IntoMap
- @ClassKey(MediaOutDialogReceiver.class)
- public abstract BroadcastReceiver bindMediaOutDialogReceiver(
- MediaOutDialogReceiver broadcastReceiver);
+ @ClassKey(MediaOutputDialogReceiver.class)
+ public abstract BroadcastReceiver bindMediaOutputDialogReceiver(
+ MediaOutputDialogReceiver broadcastReceiver);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index 38e12a6ed5f8..eb431274b8a3 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -288,6 +288,7 @@ public class DependencyProvider {
/** */
@Provides
+ @SysUISingleton
public LockPatternUtils provideLockPatternUtils(Context context) {
return new LockPatternUtils(context);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index b35579d3624b..79925bad3cc7 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -62,6 +62,7 @@ import android.view.ViewConfiguration;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
+import android.view.inputmethod.InputMethodManager;
import com.android.internal.app.IBatteryStats;
import com.android.internal.statusbar.IStatusBarService;
@@ -183,6 +184,12 @@ public class FrameworkServicesModule {
@Provides
@Singleton
+ static InputMethodManager provideInputMethodManager(Context context) {
+ return context.getSystemService(InputMethodManager.class);
+ }
+
+ @Provides
+ @Singleton
static IPackageManager provideIPackageManager() {
return IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 424a8246b278..6154a4ecb85c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -198,6 +198,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..46cec95a967a 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -143,6 +143,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 1ef806c8bd68..1e0460bea5be 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:
@@ -432,8 +434,12 @@ public class DozeMachine {
/** Give the Part a chance to clean itself up. */
default void destroy() {}
- /** Alerts that the screenstate is being changed. */
- default void onScreenState(int state) {}
+ /**
+ * Alerts that the screenstate is being changed.
+ * Note: This may be called from within a call to transitionTo, so local DozeState may not
+ * be accurate nor match with the new displayState.
+ */
+ default void onScreenState(int displayState) {}
/** Sets the {@link DozeMachine} when this Part is associated with one. */
default void setDozeMachine(DozeMachine dozeMachine) {}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index b872b1a4219a..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;
@@ -73,7 +74,6 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi
* --ei brightness_bucket 1}
*/
private int mDebugBrightnessBucket = -1;
- private DozeMachine.State mState;
@Inject
public DozeScreenBrightness(Context context, @WrappedService DozeMachine.Service service,
@@ -94,7 +94,6 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi
@Override
public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
- mState = newState;
switch (newState) {
case INITIALIZED:
case DOZE:
@@ -112,10 +111,7 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi
@Override
public void onScreenState(int state) {
- if (!mScreenOff
- && (mState == DozeMachine.State.DOZE_AOD
- || mState == DozeMachine.State.DOZE_AOD_DOCKED)
- && (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND)) {
+ if (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND) {
setLightSensorEnabled(true);
} else {
setLightSensorEnabled(false);
@@ -226,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/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/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 781bf8d74d88..f8f4f4df58bc 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -43,7 +43,7 @@ import androidx.core.graphics.drawable.IconCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
-import com.android.settingslib.R;
+import com.android.systemui.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutDialogReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
index d60771394ded..0ce0c020ccde 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutDialogReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
@@ -26,8 +26,8 @@ import javax.inject.Inject
/**
* BroadcastReceiver for handling media output intent
*/
-class MediaOutDialogReceiver @Inject constructor(
- private var mediaOutputDialogFactory: MediaOutputDialogFactory
+class MediaOutputDialogReceiver @Inject constructor(
+ private val mediaOutputDialogFactory: MediaOutputDialogFactory
) : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (TextUtils.equals(MediaOutputSliceConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG,
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/gestural/BackGestureTfClassifierProvider.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackGestureTfClassifierProvider.java
new file mode 100644
index 000000000000..fbed18389c40
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackGestureTfClassifierProvider.java
@@ -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.
+ */
+package com.android.systemui.navigationbar.gestural;
+
+import android.content.res.AssetManager;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class can be overridden by a vendor-specific sys UI implementation,
+ * in order to provide classification models for the Back Gesture.
+ */
+public class BackGestureTfClassifierProvider {
+ private static final String TAG = "BackGestureTfClassifierProvider";
+
+ /**
+ * Default implementation that returns an empty map.
+ * This method is overridden in vendor-specific Sys UI implementation.
+ *
+ * @param am An AssetManager to get the vocab file.
+ */
+ public Map<String, Integer> loadVocab(AssetManager am) {
+ return new HashMap<String, Integer>();
+ }
+
+ /**
+ * This method is overridden in vendor-specific Sys UI implementation.
+ *
+ * @param featuresVector List of input features.
+ *
+ */
+ public float predict(Object[] featuresVector) {
+ return -1;
+ }
+
+ /**
+ * Interpreter owns resources. This method releases the resources after
+ * use to avoid memory leak.
+ * This method is overridden in vendor-specific Sys UI implementation.
+ *
+ */
+ public void release() {}
+
+ /**
+ * Returns whether to use the ML model for Back Gesture.
+ * This method is overridden in vendor-specific Sys UI implementation.
+ *
+ */
+ public boolean isActive() {
+ return false;
+ }
+}
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 dfc82f120c90..56943604e9b3 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -40,7 +40,6 @@ import android.util.Log;
import android.util.TypedValue;
import android.view.Choreographer;
import android.view.ISystemGestureExclusionListener;
-import android.view.InputChannel;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.InputMonitor;
@@ -56,6 +55,7 @@ import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.policy.GestureNavigationSettingsObserver;
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;
@@ -79,6 +79,7 @@ import com.android.systemui.tracing.nano.SystemUiTraceProto;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.Executor;
/**
@@ -120,8 +121,31 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa
public void onTaskStackChanged() {
mGestureBlockingActivityRunning = isGestureBlockingActivityRunning();
}
+ @Override
+ public void onTaskCreated(int taskId, ComponentName componentName) {
+ if (componentName != null) {
+ mPackageName = componentName.getPackageName();
+ } else {
+ mPackageName = "_UNKNOWN";
+ }
+ }
};
+ private DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
+ new DeviceConfig.OnPropertiesChangedListener() {
+ @Override
+ public void onPropertiesChanged(DeviceConfig.Properties properties) {
+ if (DeviceConfig.NAMESPACE_SYSTEMUI.equals(properties.getNamespace())
+ && (properties.getKeyset().contains(
+ SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_THRESHOLD)
+ || properties.getKeyset().contains(
+ SystemUiDeviceConfigFlags.USE_BACK_GESTURE_ML_MODEL))) {
+ updateMLModelState();
+ }
+ }
+ };
+
+
private final Context mContext;
private final OverviewProxyService mOverviewProxyService;
private final SysUiState mSysUiState;
@@ -177,6 +201,13 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa
private int mRightInset;
private int mSysUiFlags;
+ // For Tf-Lite model.
+ private BackGestureTfClassifierProvider mBackGestureTfClassifierProvider;
+ private Map<String, Integer> mVocab;
+ private boolean mUseMLModel;
+ private float mMLModelThreshold;
+ private String mPackageName;
+
private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver;
private final NavigationEdgeBackPlugin.BackCallback mBackCallback =
@@ -242,7 +273,6 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa
Log.e(TAG, "Failed to add gesture blocking activities", e);
}
}
-
mLongPressTimeout = Math.min(MAX_LONG_PRESS_TIMEOUT,
ViewConfiguration.getLongPressTimeout());
@@ -357,6 +387,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa
mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
mPluginManager.removePluginListener(this);
ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
+ DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);
try {
WindowManagerGlobal.getWindowManagerService()
@@ -372,6 +403,9 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa
mContext.getSystemService(DisplayManager.class).registerDisplayListener(this,
mContext.getMainThreadHandler());
ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+ runnable -> (mContext.getMainThreadHandler()).post(runnable),
+ mOnPropertiesChangedListener);
try {
WindowManagerGlobal.getWindowManagerService()
@@ -393,6 +427,8 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa
mPluginManager.addPluginListener(
this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false);
}
+ // Update the ML model resources.
+ updateMLModelState();
}
@Override
@@ -445,26 +481,87 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa
}
}
- private boolean isWithinTouchRegion(int x, int y) {
- // Disallow if we are in the bottom gesture area
- if (y >= (mDisplaySize.y - mBottomGestureHeight)) {
- return false;
+ private void updateMLModelState() {
+ boolean newState = mIsEnabled && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.USE_BACK_GESTURE_ML_MODEL, false);
+
+ if (newState == mUseMLModel) {
+ return;
}
- // If the point is way too far (twice the margin), it is
- // not interesting to us for logging purposes, nor we
- // should process it. Simply return false and keep
- // mLogGesture = false.
- if (x > 2 * (mEdgeWidthLeft + mLeftInset)
- && x < (mDisplaySize.x - 2 * (mEdgeWidthRight + mRightInset))) {
- return false;
+ if (newState) {
+ mBackGestureTfClassifierProvider = SystemUIFactory.getInstance()
+ .createBackGestureTfClassifierProvider(mContext.getAssets());
+ mMLModelThreshold = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_THRESHOLD, 0.9f);
+ if (mBackGestureTfClassifierProvider.isActive()) {
+ mVocab = mBackGestureTfClassifierProvider.loadVocab(mContext.getAssets());
+ mUseMLModel = true;
+ return;
+ }
}
- // Denotes whether we should proceed with the gesture.
- // Even if it is false, we may want to log it assuming
- // it is not invalid due to exclusion.
- boolean withinRange = x <= mEdgeWidthLeft + mLeftInset
- || x >= (mDisplaySize.x - mEdgeWidthRight - mRightInset);
+ mUseMLModel = false;
+ if (mBackGestureTfClassifierProvider != null) {
+ mBackGestureTfClassifierProvider.release();
+ mBackGestureTfClassifierProvider = null;
+ }
+ }
+
+ private float getBackGesturePredictionsCategory(int x, int y) {
+ if (!mVocab.containsKey(mPackageName)) {
+ return -1;
+ }
+
+ int distanceFromEdge;
+ int location;
+ if (x <= mDisplaySize.x / 2.0) {
+ location = 1; // left
+ distanceFromEdge = x;
+ } else {
+ location = 2; // right
+ distanceFromEdge = mDisplaySize.x - x;
+ }
+
+ Object[] featuresVector = {
+ new long[]{(long) mDisplaySize.x},
+ new long[]{(long) distanceFromEdge},
+ new long[]{(long) location},
+ new long[]{(long) mVocab.get(mPackageName)},
+ new long[]{(long) y},
+ };
+
+ final float results = mBackGestureTfClassifierProvider.predict(featuresVector);
+ if (results == -1) return -1;
+
+ return results >= mMLModelThreshold ? 1 : 0;
+ }
+
+ private boolean isWithinTouchRegion(int x, int y) {
+ boolean withinRange = false;
+ float results = -1;
+
+ if (mUseMLModel && (results = getBackGesturePredictionsCategory(x, y)) != -1) {
+ withinRange = results == 1 ? true : false;
+ } else {
+ // Disallow if we are in the bottom gesture area
+ if (y >= (mDisplaySize.y - mBottomGestureHeight)) {
+ return false;
+ }
+ // If the point is way too far (twice the margin), it is
+ // not interesting to us for logging purposes, nor we
+ // should process it. Simply return false and keep
+ // mLogGesture = false.
+ if (x > 2 * (mEdgeWidthLeft + mLeftInset)
+ && x < (mDisplaySize.x - 2 * (mEdgeWidthRight + mRightInset))) {
+ return false;
+ }
+ // Denotes whether we should proceed with the gesture.
+ // Even if it is false, we may want to log it assuming
+ // it is not invalid due to exclusion.
+ withinRange = x <= mEdgeWidthLeft + mLeftInset
+ || x >= (mDisplaySize.x - mEdgeWidthRight - mRightInset);
+ }
// Always allow if the user is in a transient sticky immersive state
if (mIsNavBarShownTransiently) {
@@ -666,6 +763,11 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa
ActivityManager.RunningTaskInfo runningTask =
ActivityManagerWrapper.getInstance().getRunningTask();
ComponentName topActivity = runningTask == null ? null : runningTask.topActivity;
+ if (topActivity != null) {
+ mPackageName = topActivity.getPackageName();
+ } else {
+ mPackageName = "_UNKNOWN";
+ }
return topActivity != null && mGestureBlockingActivities.contains(topActivity);
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
index fd8ca8044acf..52ce7fe210ec 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
@@ -109,10 +109,11 @@ public class PipAnimationController {
@SuppressWarnings("unchecked")
PipTransitionAnimator getAnimator(SurfaceControl leash, Rect startBounds, Rect endBounds,
- Rect sourceHintRect) {
+ Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction) {
if (mCurrentAnimator == null) {
mCurrentAnimator = setupPipTransitionAnimator(
- PipTransitionAnimator.ofBounds(leash, startBounds, endBounds, sourceHintRect));
+ PipTransitionAnimator.ofBounds(leash, startBounds, endBounds, sourceHintRect,
+ direction));
} else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA
&& mCurrentAnimator.isRunning()) {
// If we are still animating the fade into pip, then just move the surface and ensure
@@ -127,7 +128,8 @@ public class PipAnimationController {
} else {
mCurrentAnimator.cancel();
mCurrentAnimator = setupPipTransitionAnimator(
- PipTransitionAnimator.ofBounds(leash, startBounds, endBounds, sourceHintRect));
+ PipTransitionAnimator.ofBounds(leash, startBounds, endBounds, sourceHintRect,
+ direction));
}
return mCurrentAnimator;
}
@@ -217,7 +219,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);
}
@@ -281,7 +283,8 @@ public class PipAnimationController {
boolean inScaleTransition() {
if (mAnimationType != ANIM_TYPE_BOUNDS) return false;
- return !isInPipDirection(getTransitionDirection());
+ final int direction = getTransitionDirection();
+ return !isInPipDirection(direction) && !isOutPipDirection(direction);
}
/**
@@ -316,7 +319,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);
@@ -357,16 +361,26 @@ public class PipAnimationController {
}
static PipTransitionAnimator<Rect> ofBounds(SurfaceControl leash,
- Rect startValue, Rect endValue, Rect sourceHintRect) {
+ Rect startValue, Rect endValue, Rect sourceHintRect,
+ @PipAnimationController.TransitionDirection int direction) {
// Just for simplicity we'll interpolate between the source rect hint insets and empty
// insets to calculate the window crop
- final Rect initialStartValue = new Rect(startValue);
- final Rect sourceHintRectInsets = sourceHintRect != null
- ? new Rect(sourceHintRect.left - startValue.left,
- sourceHintRect.top - startValue.top,
- startValue.right - sourceHintRect.right,
- startValue.bottom - sourceHintRect.bottom)
- : null;
+ final Rect initialSourceValue;
+ if (isOutPipDirection(direction)) {
+ initialSourceValue = new Rect(endValue);
+ } else {
+ initialSourceValue = new Rect(startValue);
+ }
+
+ final Rect sourceHintRectInsets;
+ if (sourceHintRect == null) {
+ sourceHintRectInsets = null;
+ } else {
+ sourceHintRectInsets = new Rect(sourceHintRect.left - initialSourceValue.left,
+ sourceHintRect.top - initialSourceValue.top,
+ initialSourceValue.right - sourceHintRect.right,
+ initialSourceValue.bottom - sourceHintRect.bottom);
+ }
final Rect sourceInsets = new Rect(0, 0, 0, 0);
// construct new Rect instances in case they are recycled
@@ -382,21 +396,23 @@ public class PipAnimationController {
final Rect end = getEndValue();
Rect bounds = mRectEvaluator.evaluate(fraction, start, end);
setCurrentValue(bounds);
- if (inScaleTransition()) {
- if (isOutPipDirection(getTransitionDirection())) {
+ if (inScaleTransition() || sourceHintRect == null) {
+ if (isOutPipDirection(direction)) {
getSurfaceTransactionHelper().scale(tx, leash, end, bounds);
} else {
getSurfaceTransactionHelper().scale(tx, leash, start, bounds);
}
} else {
- if (sourceHintRectInsets != null) {
- Rect insets = mInsetsEvaluator.evaluate(fraction, sourceInsets,
- sourceHintRectInsets);
- getSurfaceTransactionHelper().scaleAndCrop(tx, leash, initialStartValue,
- bounds, insets);
+ final Rect insets;
+ if (isOutPipDirection(direction)) {
+ insets = mInsetsEvaluator.evaluate(fraction, sourceHintRectInsets,
+ sourceInsets);
} else {
- getSurfaceTransactionHelper().scale(tx, leash, start, bounds);
+ insets = mInsetsEvaluator.evaluate(fraction, sourceInsets,
+ sourceHintRectInsets);
}
+ getSurfaceTransactionHelper().scaleAndCrop(tx, leash,
+ initialSourceValue, bounds, insets);
}
tx.apply();
}
@@ -411,13 +427,20 @@ 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
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index 9ca5f2a0dfd1..6cc0cd879d71 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -62,6 +62,7 @@ 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.systemui.pip.phone.PipUtils;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -260,6 +261,11 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize
mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
mSplitScreenOptional = splitScreenOptional;
mTaskOrganizer = shellTaskOrganizer;
+
+ if (!PipUtils.hasSystemFeature(context)) {
+ Log.w(TAG, "Device not support PIP feature");
+ return;
+ }
mTaskOrganizer.addListener(this, WINDOWING_MODE_PINNED);
displayController.addDisplayWindowListener(this);
}
@@ -360,8 +366,8 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize
public void onTransactionReady(int id, SurfaceControl.Transaction t) {
t.apply();
scheduleAnimateResizePip(mLastReportedBounds, destinationBounds,
- null /* sourceHintRect */, direction, animationDurationMs,
- null /* updateBoundsCallback */);
+ getValidSourceHintRect(mTaskInfo, destinationBounds), direction,
+ animationDurationMs, null /* updateBoundsCallback */);
mState = State.EXITING_PIP;
}
});
@@ -998,7 +1004,7 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize
return;
}
mPipAnimationController
- .getAnimator(mLeash, currentBounds, destinationBounds, sourceHintRect)
+ .getAnimator(mLeash, currentBounds, destinationBounds, sourceHintRect, direction)
.setTransitionDirection(direction)
.setPipAnimationCallback(mPipAnimationCallback)
.setDuration(durationMs)
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipController.java
index 4f225e2f9590..8a2e9e23343c 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipController.java
@@ -18,7 +18,6 @@ package com.android.systemui.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;
@@ -30,7 +29,6 @@ import android.app.IActivityManager;
import android.app.RemoteAction;
import android.content.ComponentName;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -278,12 +276,25 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
mContext = context;
mActivityManager = ActivityManager.getService();
- PackageManager pm = context.getPackageManager();
- boolean supportsPip = pm.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
- if (!supportsPip) {
+ if (PipUtils.hasSystemFeature(mContext)) {
+ initController(context, broadcastDispatcher, configController, deviceConfig,
+ displayController, floatingContentCoordinator, sysUiState, pipBoundsHandler,
+ pipSurfaceTransactionHelper, pipTaskOrganizer, pipUiEventLogger);
+ } else {
Log.w(TAG, "Device not support PIP feature");
- return;
}
+ }
+
+ private void initController(Context context, BroadcastDispatcher broadcastDispatcher,
+ ConfigurationController configController,
+ DeviceConfigProxy deviceConfig,
+ DisplayController displayController,
+ FloatingContentCoordinator floatingContentCoordinator,
+ SysUiState sysUiState,
+ PipBoundsHandler pipBoundsHandler,
+ PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
+ PipTaskOrganizer pipTaskOrganizer,
+ PipUiEventLogger pipUiEventLogger) {
// Ensure that we are the primary user's SystemUI.
final int processUser = UserManager.get(context).getUserHandle();
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java
index baa8f118f362..1bf6dd7bb9ef 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java
@@ -18,6 +18,7 @@ package com.android.systemui.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;
@@ -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/PipControlsViewController.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsViewController.java
index 4ecd52f8adf5..2f4df1fcbe20 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsViewController.java
@@ -36,6 +36,8 @@ import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
+import javax.inject.Inject;
+
/**
* Controller for {@link PipControlsView}.
*/
@@ -108,7 +110,7 @@ public class PipControlsViewController {
}
};
-
+ @Inject
public PipControlsViewController(PipControlsView view, Optional<Pip> pipOptional,
LayoutInflater layoutInflater, @Main Handler handler) {
super();
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/dagger/TvPipModule.java b/packages/SystemUI/src/com/android/systemui/pip/tv/dagger/TvPipModule.java
new file mode 100644
index 000000000000..e8e392069ea7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/dagger/TvPipModule.java
@@ -0,0 +1,92 @@
+/*
+ * 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 android.app.Activity;
+import android.content.Context;
+
+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.pip.PipUiEventLogger;
+import com.android.systemui.pip.tv.PipController;
+import com.android.systemui.pip.tv.PipMenuActivity;
+import com.android.systemui.pip.tv.PipNotification;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.splitscreen.SplitScreen;
+
+import java.util.Optional;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.Provides;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
+
+/**
+ * Dagger module for TV Pip.
+ */
+@Module(subcomponents = {TvPipComponent.class})
+public abstract class TvPipModule {
+
+ /** Inject into PipMenuActivity. */
+ @Binds
+ @IntoMap
+ @ClassKey(PipMenuActivity.class)
+ public abstract Activity providePipMenuActivity(PipMenuActivity activity);
+
+ @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 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);
+ }
+}
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 db77e08c204b..73c6504b9983 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -80,8 +80,6 @@ public class TileQueryHelper {
mFinished = false;
// Enqueue jobs to fetch every system tile and then ever package tile.
addCurrentAndStockTiles(host);
-
- addPackageTiles(host);
}
public boolean isFinished() {
@@ -122,23 +120,86 @@ public class TileQueryHelper {
tile.destroy();
continue;
}
- tile.setListening(this, true);
- tile.refreshState();
- tile.setListening(this, false);
tile.setTileSpec(spec);
tilesToAdd.add(tile);
}
- mBgExecutor.execute(() -> {
- for (QSTile tile : tilesToAdd) {
- final QSTile.State state = tile.getState().copy();
- // Ignore the current state and get the generic label instead.
- state.label = tile.getTileLabel();
- tile.destroy();
- addTile(tile.getTileSpec(), null, state, true);
+ new TileCollector(tilesToAdd, host).startListening();
+ }
+
+ private static class TilePair {
+ QSTile mTile;
+ boolean mReady = false;
+ }
+
+ private class TileCollector implements QSTile.Callback {
+
+ private final List<TilePair> mQSTileList = new ArrayList<>();
+ private final QSTileHost mQSTileHost;
+
+ TileCollector(List<QSTile> tilesToAdd, QSTileHost host) {
+ for (QSTile tile: tilesToAdd) {
+ TilePair pair = new TilePair();
+ pair.mTile = tile;
+ mQSTileList.add(pair);
}
+ mQSTileHost = host;
+ if (tilesToAdd.isEmpty()) {
+ mBgExecutor.execute(this::finished);
+ }
+ }
+
+ private void finished() {
notifyTilesChanged(false);
- });
+ addPackageTiles(mQSTileHost);
+ }
+
+ private void startListening() {
+ for (TilePair pair: mQSTileList) {
+ pair.mTile.addCallback(this);
+ pair.mTile.setListening(this, true);
+ // Make sure that at least one refresh state happens
+ pair.mTile.refreshState();
+ }
+ }
+
+ // This is called in the Bg thread
+ @Override
+ public void onStateChanged(State s) {
+ boolean allReady = true;
+ for (TilePair pair: mQSTileList) {
+ if (!pair.mReady && pair.mTile.isTileReady()) {
+ pair.mTile.removeCallback(this);
+ pair.mTile.setListening(this, false);
+ pair.mReady = true;
+ } else if (!pair.mReady) {
+ allReady = false;
+ }
+ }
+ if (allReady) {
+ for (TilePair pair : mQSTileList) {
+ QSTile tile = pair.mTile;
+ final QSTile.State state = tile.getState().copy();
+ // Ignore the current state and get the generic label instead.
+ state.label = tile.getTileLabel();
+ tile.destroy();
+ addTile(tile.getTileSpec(), null, state, true);
+ }
+ finished();
+ }
+ }
+
+ @Override
+ public void onShowDetail(boolean show) {}
+
+ @Override
+ public void onToggleStateChanged(boolean state) {}
+
+ @Override
+ public void onScanStateChanged(boolean state) {}
+
+ @Override
+ public void onAnnouncementRequested(CharSequence announcement) {}
}
private void addPackageTiles(final QSTileHost host) {
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 255513a31c75..dfd7e2c8fdb7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -90,6 +90,10 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
private static final long DEFAULT_STALE_TIMEOUT = 10 * DateUtils.MINUTE_IN_MILLIS;
protected static final Object ARG_SHOW_TRANSIENT_ENABLING = new Object();
+ private static final int READY_STATE_NOT_READY = 0;
+ private static final int READY_STATE_READYING = 1;
+ private static final int READY_STATE_READY = 2;
+
protected final QSHost mHost;
protected final Context mContext;
// @NonFinalForTesting
@@ -101,6 +105,7 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
protected final ActivityStarter mActivityStarter;
private final UiEventLogger mUiEventLogger;
private final QSLogger mQSLogger;
+ private volatile int mReadyState;
private final ArrayList<Callback> mCallbacks = new ArrayList<>();
private final Object mStaleListener = new Object();
@@ -386,7 +391,11 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
protected void handleRefreshState(Object arg) {
handleUpdateState(mTmpState, arg);
- final boolean changed = mTmpState.copyTo(mState);
+ boolean changed = mTmpState.copyTo(mState);
+ if (mReadyState == READY_STATE_READYING) {
+ mReadyState = READY_STATE_READY;
+ changed = true;
+ }
if (changed) {
mQSLogger.logTileUpdated(mTileSpec, mState);
handleStateChanged();
@@ -459,6 +468,9 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
// should not refresh it anymore.
if (mLifecycle.getCurrentState().equals(DESTROYED)) return;
mLifecycle.setCurrentState(RESUMED);
+ if (mReadyState == READY_STATE_NOT_READY) {
+ mReadyState = READY_STATE_READYING;
+ }
refreshState(); // Ensure we get at least one refresh after listening.
});
}
@@ -531,6 +543,15 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
*/
public abstract CharSequence getTileLabel();
+ /**
+ * @return {@code true} if the tile has refreshed state at least once after having set its
+ * lifecycle to {@link Lifecycle.State#RESUMED}.
+ */
+ @Override
+ public boolean isTileReady() {
+ return mReadyState == READY_STATE_READY;
+ }
+
public static int getColorForState(Context context, int state) {
switch (state) {
case Tile.STATE_UNAVAILABLE:
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 2a976f546ba4..ed8da7c3d80b 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;
@@ -65,6 +64,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;
@@ -77,6 +77,7 @@ 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.pip.phone.PipUtils;
import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.shared.recents.IOverviewProxy;
@@ -155,7 +156,8 @@ 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) {
@@ -624,7 +626,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;
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 23270630c7da..01333f0a47d5 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
@@ -23,7 +23,6 @@ import android.content.pm.ShortcutManager;
import android.os.Handler;
import android.view.accessibility.AccessibilityManager;
-import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.R;
@@ -68,7 +67,6 @@ import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerImpl;
import com.android.systemui.statusbar.notification.row.ChannelEditorDialogController;
-import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
import com.android.systemui.statusbar.notification.row.PriorityOnboardingDialogController;
@@ -198,20 +196,6 @@ public interface NotificationsModule {
return new NotificationPanelLoggerImpl();
}
- /** Provides an instance of {@link NotificationBlockingHelperManager} */
- @SysUISingleton
- @Provides
- static NotificationBlockingHelperManager provideNotificationBlockingHelperManager(
- Context context,
- NotificationGutsManager notificationGutsManager,
- NotificationEntryManager notificationEntryManager,
- MetricsLogger metricsLogger,
- GroupMembershipManager groupMembershipManager) {
- return new NotificationBlockingHelperManager(
- context, notificationGutsManager, notificationEntryManager, metricsLogger,
- groupMembershipManager);
- }
-
/** Provides an instance of {@link GroupMembershipManager} */
@SysUISingleton
@Provides
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 adda049951ac..811a72de093c 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
@@ -500,10 +500,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
* or is in a whitelist).
*/
public boolean getIsNonblockable() {
- boolean isNonblockable = Dependency.get(NotificationBlockingHelperManager.class)
- .isNonblockable(mEntry.getSbn().getPackageName(),
- mEntry.getChannel().getId());
-
// If the SystemNotifAsyncTask hasn't finished running or retrieved a value, we'll try once
// again, but in-place on the main thread this time. This should rarely ever get called.
if (mEntry != null && mEntry.mIsSystemNotification == null) {
@@ -514,13 +510,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mEntry.mIsSystemNotification = isSystemNotification(mContext, mEntry.getSbn());
}
- isNonblockable |= mEntry.getChannel().isImportanceLockedByOEM();
- isNonblockable |= mEntry.getChannel().isImportanceLockedByCriticalDeviceFunction();
+ boolean isNonblockable = mEntry.getChannel().isImportanceLockedByOEM()
+ || mEntry.getChannel().isImportanceLockedByCriticalDeviceFunction();
if (!isNonblockable && mEntry != null && mEntry.mIsSystemNotification != null) {
if (mEntry.mIsSystemNotification) {
- if (mEntry.getChannel() != null
- && !mEntry.getChannel().isBlockable()) {
+ if (mEntry.getChannel() != null && !mEntry.getChannel().isBlockable()) {
isNonblockable = true;
}
}
@@ -1398,26 +1393,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
/**
- * Dismisses the notification with the option of showing the blocking helper in-place if we have
- * a negative user sentiment.
+ * Dismisses the notification.
*
* @param fromAccessibility whether this dismiss is coming from an accessibility action
- * @return whether a blocking helper is shown in this row
*/
- public boolean performDismissWithBlockingHelper(boolean fromAccessibility) {
- NotificationBlockingHelperManager manager =
- Dependency.get(NotificationBlockingHelperManager.class);
- boolean isBlockingHelperShown = manager.perhapsShowBlockingHelper(this, mMenuRow);
-
- Dependency.get(MetricsLogger.class).count(NotificationCounters.NOTIFICATION_DISMISSED, 1);
-
- // Continue with dismiss since we don't want the blocking helper to be directly associated
- // with a certain notification.
- performDismiss(fromAccessibility);
- return isBlockingHelperShown;
- }
-
public void performDismiss(boolean fromAccessibility) {
+ Dependency.get(MetricsLogger.class).count(NotificationCounters.NOTIFICATION_DISMISSED, 1);
dismiss(fromAccessibility);
if (mEntry.isClearable()) {
if (mOnUserInteractionCallback != null) {
@@ -2983,7 +2964,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
switch (action) {
case AccessibilityNodeInfo.ACTION_DISMISS:
- performDismissWithBlockingHelper(true /* fromAccessibility */);
+ performDismiss(true /* fromAccessibility */);
return true;
case AccessibilityNodeInfo.ACTION_COLLAPSE:
case AccessibilityNodeInfo.ACTION_EXPAND:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
index eeac46a60ac8..7071b73c2ebf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
@@ -295,10 +295,6 @@ public class NotificationGuts extends FrameLayout {
*/
private void closeControls(int x, int y, boolean save, boolean force) {
// First try to dismiss any blocking helper.
- boolean wasBlockingHelperDismissed =
- Dependency.get(NotificationBlockingHelperManager.class)
- .dismissCurrentBlockingHelper();
-
if (getWindowToken() == null) {
if (mClosedListener != null) {
mClosedListener.onGutsClosed(this);
@@ -307,10 +303,9 @@ public class NotificationGuts extends FrameLayout {
}
if (mGutsContent == null
- || !mGutsContent.handleCloseControls(save, force)
- || wasBlockingHelperDismissed) {
+ || !mGutsContent.handleCloseControls(save, force)) {
// We only want to do a circular reveal if we're not showing the blocking helper.
- animateClose(x, y, !wasBlockingHelperDismissed /* shouldDoCircularReveal */);
+ animateClose(x, y, true /* shouldDoCircularReveal */);
setExposed(false, mNeedsFalsingProtection);
if (mClosedListener != null) {
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 7a12464bd4ff..500de2d29d03 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
@@ -106,7 +106,6 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.FooterView;
import com.android.systemui.statusbar.notification.row.ForegroundServiceDungeonView;
-import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
@@ -449,12 +448,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private DismissListener mDismissListener;
private DismissAllAnimationListener mDismissAllAnimationListener;
private NotificationRemoteInputManager mRemoteInputManager;
+ private ShadeController mShadeController;
private final DisplayMetrics mDisplayMetrics = Dependency.get(DisplayMetrics.class);
private final LockscreenGestureLogger mLockscreenGestureLogger =
Dependency.get(LockscreenGestureLogger.class);
- private final VisualStabilityManager mVisualStabilityManager =
- Dependency.get(VisualStabilityManager.class);
protected boolean mClearAllEnabled;
private Interpolator mHideXInterpolator = Interpolators.FAST_OUT_SLOW_IN;
@@ -553,13 +551,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
res.getBoolean(R.bool.config_drawNotificationBackground);
setOutlineProvider(mOutlineProvider);
- // Blocking helper manager wants to know the expanded state, update as well.
- NotificationBlockingHelperManager blockingHelperManager =
- Dependency.get(NotificationBlockingHelperManager.class);
- addOnExpandedHeightChangedListener((height, unused) -> {
- blockingHelperManager.setNotificationShadeExpanded(height);
- });
-
boolean willDraw = mShouldDrawNotificationBackground || DEBUG;
setWillNotDraw(!willDraw);
mBackgroundPaint.setAntiAlias(true);
@@ -580,9 +571,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (mFgsSectionView != null) {
return;
}
-
mFgsSectionView = fgsSectionView;
-
addView(mFgsSectionView, -1);
}
@@ -603,7 +592,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
inflateEmptyShadeView();
inflateFooterView();
- mVisualStabilityManager.setVisibilityLocationProvider(this::isInVisibleLocation);
}
/**
@@ -1021,23 +1009,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
@ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
- public boolean isInVisibleLocation(NotificationEntry entry) {
- ExpandableNotificationRow row = entry.getRow();
- ExpandableViewState childViewState = row.getViewState();
-
- if (childViewState == null) {
- return false;
- }
- if ((childViewState.location & ExpandableViewState.VISIBLE_LOCATIONS) == 0) {
- return false;
- }
- if (row.getVisibility() != View.VISIBLE) {
- return false;
- }
- return true;
- }
-
- @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
private void setMaxLayoutHeight(int maxLayoutHeight) {
mMaxLayoutHeight = maxLayoutHeight;
mShelf.setMaxLayoutHeight(maxLayoutHeight);
@@ -5371,9 +5342,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
if (viewsToRemove.isEmpty()) {
- if (closeShade) {
- Dependency.get(ShadeController.class).animateCollapsePanels(
- CommandQueue.FLAG_EXCLUDE_NONE);
+ if (closeShade && mShadeController != null) {
+ mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
}
return;
}
@@ -5409,11 +5379,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
final Runnable onSlideAwayAnimationComplete = () -> {
if (closeShade) {
- Dependency.get(ShadeController.class).addPostCollapseAction(() -> {
+ mShadeController.addPostCollapseAction(() -> {
setDismissAllInProgress(false);
onAnimationComplete.run();
});
- Dependency.get(ShadeController.class).animateCollapsePanels(
+ mShadeController.animateCollapsePanels(
CommandQueue.FLAG_EXCLUDE_NONE);
} else {
setDismissAllInProgress(false);
@@ -5697,6 +5667,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mRemoteInputManager = remoteInputManager;
}
+ void setShadeController(ShadeController shadeController) {
+ mShadeController = shadeController;
+ }
+
/**
* A listener that is notified when the empty space below the notifications is clicked on
*/
@@ -6096,7 +6070,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (!mAmbientState.isDozing() || startingChild != null) {
// We have notifications, go to locked shade.
- Dependency.get(ShadeController.class).goToLockedShade(startingChild);
+ mShadeController.goToLockedShade(startingChild);
if (startingChild instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) startingChild;
row.onExpandedByGesture(true /* drag down is always an open */);
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 231ff2cec8f5..703c214ed3ac 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
@@ -21,11 +21,11 @@ import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_N
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.ROWS_GENTLE;
-import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_HIGH_PRIORITY;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnEmptySpaceClickListener;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnOverscrollTopChangedListener;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
+import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
+import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_HIGH_PRIORITY;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.SelectedRows;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.canChildBeDismissed;
import static com.android.systemui.statusbar.phone.NotificationIconAreaController.HIGH_PRIORITY;
@@ -85,6 +85,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.NotificationGroup;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.OnGroupChangeListener;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
@@ -104,6 +105,7 @@ import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationPanelViewController;
import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -139,7 +141,6 @@ public class NotificationStackScrollLayoutController {
private final ZenModeController mZenModeController;
private final MetricsLogger mMetricsLogger;
private final FalsingManager mFalsingManager;
- private final NotificationSectionsManager mNotificationSectionsManager;
private final Resources mResources;
private final NotificationSwipeHelper.Builder mNotificationSwipeHelperBuilder;
private final ScrimController mScrimController;
@@ -153,6 +154,8 @@ public class NotificationStackScrollLayoutController {
private final ForegroundServiceSectionController mFgServicesSectionController;
private final LayoutInflater mLayoutInflater;
private final NotificationRemoteInputManager mRemoteInputManager;
+ private final VisualStabilityManager mVisualStabilityManager;
+ private final ShadeController mShadeController;
private final KeyguardMediaController mKeyguardMediaController;
private final SysuiStatusBarStateController mStatusBarStateController;
private final KeyguardBypassController mKeyguardBypassController;
@@ -389,8 +392,6 @@ public class NotificationStackScrollLayoutController {
return;
}
- boolean isBlockingHelperShown = false;
-
mView.removeDraggedView(view);
mView.updateContinuousShadowDrawing();
@@ -400,13 +401,10 @@ public class NotificationStackScrollLayoutController {
mHeadsUpManager.addSwipedOutNotification(
row.getEntry().getSbn().getKey());
}
- isBlockingHelperShown =
- row.performDismissWithBlockingHelper(false /* fromAccessibility */);
+ row.performDismiss(false /* fromAccessibility */);
}
- if (!isBlockingHelperShown) {
- mView.addSwipedOutView(view);
- }
+ mView.addSwipedOutView(view);
mFalsingManager.onNotificationDismissed();
if (mFalsingManager.shouldEnforceBouncer()) {
mStatusBar.executeRunnableDismissingKeyguard(
@@ -559,7 +557,6 @@ public class NotificationStackScrollLayoutController {
NotificationLockscreenUserManager lockscreenUserManager,
MetricsLogger metricsLogger,
FalsingManager falsingManager,
- NotificationSectionsManager notificationSectionsManager,
@Main Resources resources,
NotificationSwipeHelper.Builder notificationSwipeHelperBuilder,
StatusBar statusBar,
@@ -576,7 +573,9 @@ public class NotificationStackScrollLayoutController {
ForegroundServiceDismissalFeatureController fgFeatureController,
ForegroundServiceSectionController fgServicesSectionController,
LayoutInflater layoutInflater,
- NotificationRemoteInputManager remoteInputManager) {
+ NotificationRemoteInputManager remoteInputManager,
+ VisualStabilityManager visualStabilityManager,
+ ShadeController shadeController) {
mAllowLongPress = allowLongPress;
mNotificationGutsManager = notificationGutsManager;
mHeadsUpManager = headsUpManager;
@@ -592,14 +591,12 @@ public class NotificationStackScrollLayoutController {
mLockscreenUserManager = lockscreenUserManager;
mMetricsLogger = metricsLogger;
mFalsingManager = falsingManager;
- mNotificationSectionsManager = notificationSectionsManager;
mResources = resources;
mNotificationSwipeHelperBuilder = notificationSwipeHelperBuilder;
mStatusBar = statusBar;
mScrimController = scrimController;
- groupManager.registerGroupExpansionChangeListener((changedRow, expanded) -> {
- mView.onGroupExpandChanged(changedRow, expanded);
- });
+ groupManager.registerGroupExpansionChangeListener(
+ (changedRow, expanded) -> mView.onGroupExpandChanged(changedRow, expanded));
legacyGroupManager.registerGroupChangeListener(new OnGroupChangeListener() {
@Override
public void onGroupCreatedFromChildren(NotificationGroup group) {
@@ -622,6 +619,8 @@ public class NotificationStackScrollLayoutController {
mFgServicesSectionController = fgServicesSectionController;
mLayoutInflater = layoutInflater;
mRemoteInputManager = remoteInputManager;
+ mVisualStabilityManager = visualStabilityManager;
+ mShadeController = shadeController;
}
public void attach(NotificationStackScrollLayout view) {
@@ -635,6 +634,7 @@ public class NotificationStackScrollLayoutController {
mView.setFooterDismissListener(() ->
mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES));
mView.setRemoteInputManager(mRemoteInputManager);
+ mView.setShadeController(mShadeController);
if (mFgFeatureController.isForegroundServiceDismissalEnabled()) {
mView.initializeForegroundServiceSection(
@@ -681,6 +681,8 @@ public class NotificationStackScrollLayoutController {
mNotificationRoundnessManager.setOnRoundingChangedCallback(mView::invalidate);
mView.addOnExpandedHeightChangedListener(mNotificationRoundnessManager::setExpanded);
+ mVisualStabilityManager.setVisibilityLocationProvider(this::isInVisibleLocation);
+
mTunerService.addTunable(
(key, newValue) -> {
switch (key) {
@@ -723,6 +725,22 @@ public class NotificationStackScrollLayoutController {
mSilentHeaderController.setOnClearAllClickListener(v -> clearSilentNotifications());
}
+ private boolean isInVisibleLocation(NotificationEntry entry) {
+ ExpandableNotificationRow row = entry.getRow();
+ ExpandableViewState childViewState = row.getViewState();
+
+ if (childViewState == null) {
+ return false;
+ }
+ if ((childViewState.location & ExpandableViewState.VISIBLE_LOCATIONS) == 0) {
+ return false;
+ }
+ if (row.getVisibility() != View.VISIBLE) {
+ return false;
+ }
+ return true;
+ }
+
public void addOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener) {
mView.addOnExpandedHeightChangedListener(listener);
}
@@ -1459,7 +1477,7 @@ public class NotificationStackScrollLayoutController {
@Override
public boolean isInVisibleLocation(NotificationEntry entry) {
- return mView.isInVisibleLocation(entry);
+ return NotificationStackScrollLayoutController.this.isInVisibleLocation(entry);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 3665c39bd9ea..af6ac223ada1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -40,12 +40,15 @@ import com.android.keyguard.ViewMediatorCallback;
import com.android.keyguard.dagger.KeyguardBouncerComponent;
import com.android.keyguard.dagger.RootView;
import com.android.systemui.DejankUtils;
+import com.android.systemui.biometrics.AuthController;
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
import javax.inject.Inject;
@@ -66,7 +69,8 @@ public class KeyguardBouncer {
private final FalsingManager mFalsingManager;
private final DismissCallbackRegistry mDismissCallbackRegistry;
private final Handler mHandler;
- private final BouncerExpansionCallback mExpansionCallback;
+ private final List<BouncerExpansionCallback> mExpansionCallbacks = new ArrayList<>();
+ private final AuthController mAuthController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final KeyguardStateController mKeyguardStateController;
private final KeyguardSecurityModel mKeyguardSecurityModel;
@@ -100,6 +104,7 @@ public class KeyguardBouncer {
ViewGroup container,
DismissCallbackRegistry dismissCallbackRegistry, FalsingManager falsingManager,
BouncerExpansionCallback expansionCallback,
+ AuthController authController,
KeyguardStateController keyguardStateController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
KeyguardBypassController keyguardBypassController, Handler handler,
@@ -111,13 +116,15 @@ public class KeyguardBouncer {
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mFalsingManager = falsingManager;
mDismissCallbackRegistry = dismissCallbackRegistry;
- mExpansionCallback = expansionCallback;
mHandler = handler;
mKeyguardStateController = keyguardStateController;
mKeyguardSecurityModel = keyguardSecurityModel;
mKeyguardBouncerComponentFactory = keyguardBouncerComponentFactory;
mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
mKeyguardBypassController = keyguardBypassController;
+ mExpansionCallbacks.add(expansionCallback);
+ mExpansionCallbacks.add(authController);
+ mAuthController = authController;
}
public void show(boolean resetSecuritySelection) {
@@ -188,7 +195,7 @@ public class KeyguardBouncer {
}
mCallback.onBouncerVisiblityChanged(true /* shown */);
- mExpansionCallback.onStartingToShow();
+ dispatchStartingToShow();
}
public boolean isScrimmed() {
@@ -290,6 +297,11 @@ public class KeyguardBouncer {
mIsScrimmed = false;
mFalsingManager.onBouncerHidden();
mCallback.onBouncerVisiblityChanged(false /* shown */);
+ // TODO(b/165257355): `mAuthController.onFullyHidden` should be `dispatchFullyHidden()`
+ // But, it is causing the UDFPS icon to disappear after SystemUI restarts. I guess the
+ // ExpansionCallback from StatusBarKeyguardViewManager can't handle the call to
+ // onFullyHidden after a restart.
+ mAuthController.onFullyHidden();
cancelShowRunnable();
if (mKeyguardViewController != null) {
mKeyguardViewController.cancelDismissAction();
@@ -382,12 +394,12 @@ public class KeyguardBouncer {
if (fraction == EXPANSION_VISIBLE && oldExpansion != EXPANSION_VISIBLE) {
onFullyShown();
- mExpansionCallback.onFullyShown();
+ dispatchFullyShown();
} else if (fraction == EXPANSION_HIDDEN && oldExpansion != EXPANSION_HIDDEN) {
onFullyHidden();
- mExpansionCallback.onFullyHidden();
+ dispatchFullyHidden();
} else if (fraction != EXPANSION_VISIBLE && oldExpansion == EXPANSION_VISIBLE) {
- mExpansionCallback.onStartingToHide();
+ dispatchStartingToHide();
if (mKeyguardViewController != null) {
mKeyguardViewController.onStartingToHide();
}
@@ -492,6 +504,30 @@ public class KeyguardBouncer {
mKeyguardViewController.finish(strongAuth, KeyguardUpdateMonitor.getCurrentUser());
}
+ private void dispatchFullyShown() {
+ for (BouncerExpansionCallback callback : mExpansionCallbacks) {
+ callback.onFullyShown();
+ }
+ }
+
+ private void dispatchStartingToHide() {
+ for (BouncerExpansionCallback callback : mExpansionCallbacks) {
+ callback.onStartingToHide();
+ }
+ }
+
+ private void dispatchStartingToShow() {
+ for (BouncerExpansionCallback callback : mExpansionCallbacks) {
+ callback.onStartingToShow();
+ }
+ }
+
+ private void dispatchFullyHidden() {
+ for (BouncerExpansionCallback callback : mExpansionCallbacks) {
+ callback.onFullyHidden();
+ }
+ }
+
public void dump(PrintWriter pw) {
pw.println("KeyguardBouncer");
pw.println(" isShowing(): " + isShowing());
@@ -516,6 +552,7 @@ public class KeyguardBouncer {
private final ViewMediatorCallback mCallback;
private final DismissCallbackRegistry mDismissCallbackRegistry;
private final FalsingManager mFalsingManager;
+ private final AuthController mAuthController;
private final KeyguardStateController mKeyguardStateController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final KeyguardBypassController mKeyguardBypassController;
@@ -526,6 +563,7 @@ public class KeyguardBouncer {
@Inject
public Factory(Context context, ViewMediatorCallback callback,
DismissCallbackRegistry dismissCallbackRegistry, FalsingManager falsingManager,
+ AuthController authController,
KeyguardStateController keyguardStateController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
KeyguardBypassController keyguardBypassController, Handler handler,
@@ -535,6 +573,7 @@ public class KeyguardBouncer {
mCallback = callback;
mDismissCallbackRegistry = dismissCallbackRegistry;
mFalsingManager = falsingManager;
+ mAuthController = authController;
mKeyguardStateController = keyguardStateController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mKeyguardBypassController = keyguardBypassController;
@@ -547,8 +586,9 @@ public class KeyguardBouncer {
BouncerExpansionCallback expansionCallback) {
return new KeyguardBouncer(mContext, mCallback, container,
mDismissCallbackRegistry, mFalsingManager, expansionCallback,
- mKeyguardStateController, mKeyguardUpdateMonitor, mKeyguardBypassController,
- mHandler, mKeyguardSecurityModel, mKeyguardBouncerComponentFactory);
+ mAuthController, mKeyguardStateController, mKeyguardUpdateMonitor,
+ mKeyguardBypassController, mHandler, mKeyguardSecurityModel,
+ mKeyguardBouncerComponentFactory);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 51209d166eee..9e2e57e7047e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.phone;
-import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
-import static android.view.ViewRootImpl.sNewInsetsMode;
import static android.view.WindowInsets.Type.navigationBars;
import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
@@ -803,12 +801,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private Runnable mMakeNavigationBarVisibleRunnable = new Runnable() {
@Override
public void run() {
- if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_FULL) {
- mStatusBar.getNotificationShadeWindowView().getWindowInsetsController()
- .show(navigationBars());
- } else {
- mStatusBar.getNavigationBarView().getRootView().setVisibility(View.VISIBLE);
- }
+ mStatusBar.getNotificationShadeWindowView().getWindowInsetsController()
+ .show(navigationBars());
}
};
@@ -876,12 +870,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
}
} else {
mContainer.removeCallbacks(mMakeNavigationBarVisibleRunnable);
- if (sNewInsetsMode == NEW_INSETS_MODE_FULL) {
- mStatusBar.getNotificationShadeWindowView().getWindowInsetsController()
- .hide(navigationBars());
- } else {
- mStatusBar.getNavigationBarView().getRootView().setVisibility(View.GONE);
- }
+ mStatusBar.getNotificationShadeWindowView().getWindowInsetsController()
+ .hide(navigationBars());
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
index 22fa0106795a..f277b30e589d 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.pip.tv.dagger.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/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
index eb8f065149c8..a6cd350b33ce 100644
--- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
@@ -23,7 +23,6 @@ import android.view.InflateException;
import android.view.LayoutInflater;
import android.view.View;
-import com.android.keyguard.KeyguardMessageArea;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.qs.QSFooterImpl;
import com.android.systemui.qs.QSPanel;
@@ -108,11 +107,6 @@ public class InjectionInflationController {
NotificationStackScrollLayout createNotificationStackScrollLayout();
/**
- * Creates the KeyguardMessageArea.
- */
- KeyguardMessageArea createKeyguardMessageArea();
-
- /**
* Creates the QSPanel.
*/
QSPanel createQSPanel();
diff --git a/packages/SystemUI/src/com/android/systemui/util/ViewController.java b/packages/SystemUI/src/com/android/systemui/util/ViewController.java
index 64f8dbbb9e34..c7aa780fcacb 100644
--- a/packages/SystemUI/src/com/android/systemui/util/ViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/ViewController.java
@@ -23,7 +23,20 @@ import android.view.View.OnAttachStateChangeListener;
* Utility class that handles view lifecycle events for View Controllers.
*
* Implementations should handle setup and teardown related activities inside of
- * {@link #onViewAttached()} and {@link #onViewDetached()}.
+ * {@link #onViewAttached()} and {@link #onViewDetached()}. Be sure to call {@link #init()} on
+ * any child controllers that this uses. This can be done in {@link init()} if the controllers
+ * are injected, or right after creation time of the child controller.
+ *
+ * Tip: View "attachment" happens top down - parents are notified that they are attached before
+ * any children. That means that if you call a method on a child controller in
+ * {@link #onViewAttached()}, the child controller may not have had its onViewAttach method
+ * called, so it may not be fully set up.
+ *
+ * As such, make sure that methods on your controller are safe to call _before_ its {@link #init()}
+ * and {@link #onViewAttached()} methods are called. Specifically, if your controller must call
+ * {@link View#findViewById(int)} on its root view to setup member variables, do so in its
+ * constructor. Save {@link #onViewAttached()} for things that can happen post-construction - adding
+ * listeners, dynamically changing content, or other runtime decisions.
*
* @param <T> View class that this ViewController is for.
*/
@@ -54,10 +67,12 @@ public abstract class ViewController<T extends View> {
}
mInited = true;
- if (mView.isAttachedToWindow()) {
- mOnAttachStateListener.onViewAttachedToWindow(mView);
+ if (mView != null) {
+ if (mView.isAttachedToWindow()) {
+ mOnAttachStateListener.onViewAttachedToWindow(mView);
+ }
+ mView.addOnAttachStateChangeListener(mOnAttachStateListener);
}
- mView.addOnAttachStateChangeListener(mOnAttachStateListener);
}
/**
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/wmshell/TvWMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
index 0869cf739d02..524eca389ceb 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
@@ -20,17 +20,9 @@ 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.systemui.pip.tv.dagger.TvPipModule;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
@@ -39,8 +31,6 @@ 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 dagger.Module;
import dagger.Provides;
@@ -49,7 +39,7 @@ 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
@@ -61,17 +51,6 @@ public class TvWMShellModule {
@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,
@@ -79,30 +58,4 @@ public class TvWMShellModule {
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);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index ce125f3fdce0..a2d6ac8f8511 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -40,6 +40,7 @@ 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.pip.phone.PipUtils;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.tracing.ProtoTraceable;
@@ -57,7 +58,6 @@ 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;
@@ -103,7 +103,6 @@ public final class WMShell extends SystemUI
ProtoTracer protoTracer) {
super(context);
mCommandQueue = commandQueue;
- mCommandQueue.addCallback(this);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mActivityManagerWrapper = activityManagerWrapper;
mDisplayImeController = displayImeController;
@@ -120,6 +119,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,6 +131,9 @@ 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() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
index 9be2d124026c..dffad6ccbea5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
@@ -41,8 +41,6 @@ import android.testing.TestableLooper.RunWithLooper;
import android.testing.ViewUtils;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceView;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
import androidx.test.filters.SmallTest;
@@ -67,7 +65,7 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase {
private ComponentName mComponentName;
private Intent mServiceIntent;
private TestableLooper mTestableLooper;
- private ViewGroup mParent;
+ private KeyguardSecurityContainer mKeyguardSecurityContainer;
@Mock
private Handler mHandler;
@@ -84,8 +82,8 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase {
public void setUp() {
MockitoAnnotations.initMocks(this);
- mParent = spy(new FrameLayout(mContext));
- ViewUtils.attachView(mParent);
+ mKeyguardSecurityContainer = spy(new KeyguardSecurityContainer(mContext));
+ ViewUtils.attachView(mKeyguardSecurityContainer);
mTestableLooper = TestableLooper.get(this);
mComponentName = new ComponentName(mContext, "FakeKeyguardClient.class");
@@ -96,13 +94,14 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase {
when(mKeyguardClient.queryLocalInterface(anyString())).thenReturn(mKeyguardClient);
when(mKeyguardClient.asBinder()).thenReturn(mKeyguardClient);
- mTestController = new AdminSecondaryLockScreenController(
- mContext, mParent, mUpdateMonitor, mKeyguardCallback, mHandler);
+ mTestController = new AdminSecondaryLockScreenController.Factory(
+ mContext, mKeyguardSecurityContainer, mUpdateMonitor, mHandler)
+ .create(mKeyguardCallback);
}
@After
public void tearDown() {
- ViewUtils.detachView(mParent);
+ ViewUtils.detachView(mKeyguardSecurityContainer);
}
@Test
@@ -146,7 +145,7 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase {
SurfaceView v = verifySurfaceReady();
mTestController.hide();
- verify(mParent).removeView(v);
+ verify(mKeyguardSecurityContainer).removeView(v);
assertThat(mContext.isBound(mComponentName)).isFalse();
}
@@ -154,7 +153,7 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase {
public void testHide_notShown() throws Exception {
mTestController.hide();
// Nothing should happen if trying to hide when the view isn't attached yet.
- verify(mParent, never()).removeView(any(SurfaceView.class));
+ verify(mKeyguardSecurityContainer, never()).removeView(any(SurfaceView.class));
}
@Test
@@ -182,7 +181,7 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase {
private SurfaceView verifySurfaceReady() throws Exception {
mTestableLooper.processAllMessages();
ArgumentCaptor<SurfaceView> captor = ArgumentCaptor.forClass(SurfaceView.class);
- verify(mParent).addView(captor.capture());
+ verify(mKeyguardSecurityContainer).addView(captor.capture());
mTestableLooper.processAllMessages();
verify(mKeyguardClient).onCreateKeyguardSurface(any(), any(IKeyguardCallback.class));
@@ -190,7 +189,7 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase {
}
private void verifyViewDismissed(SurfaceView v) throws Exception {
- verify(mParent).removeView(v);
+ verify(mKeyguardSecurityContainer).removeView(v);
verify(mKeyguardCallback).dismiss(true, TARGET_USER_ID, true);
assertThat(mContext.isBound(mComponentName)).isFalse();
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
new file mode 100644
index 000000000000..c2ade81a9877
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.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.keyguard;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.KeyEvent;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.LatencyTracker;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardAbsKeyInputView.KeyDownListener;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.R;
+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.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase {
+
+ @Mock
+ private KeyguardAbsKeyInputView mAbsKeyInputView;
+ @Mock
+ private PasswordTextView mPasswordEntry;
+ @Mock
+ private KeyguardMessageArea mKeyguardMessageArea;
+ @Mock
+ private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock
+ private SecurityMode mSecurityMode;
+ @Mock
+ private LockPatternUtils mLockPatternUtils;
+ @Mock
+ private KeyguardSecurityCallback mKeyguardSecurityCallback;
+ @Mock
+ private KeyguardMessageAreaController.Factory mKeyguardMessageAreaControllerFactory;
+ @Mock
+ private KeyguardMessageAreaController mKeyguardMessageAreaController;
+ @Mock
+ private LatencyTracker mLatencyTracker;
+
+ private KeyguardAbsKeyInputViewController mKeyguardAbsKeyInputViewController;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ when(mKeyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea.class)))
+ .thenReturn(mKeyguardMessageAreaController);
+ when(mAbsKeyInputView.getPasswordTextViewId()).thenReturn(1);
+ when(mAbsKeyInputView.findViewById(1)).thenReturn(mPasswordEntry);
+ when(mAbsKeyInputView.isAttachedToWindow()).thenReturn(true);
+ when(mAbsKeyInputView.findViewById(R.id.keyguard_message_area))
+ .thenReturn(mKeyguardMessageArea);
+ mKeyguardAbsKeyInputViewController = new KeyguardAbsKeyInputViewController(mAbsKeyInputView,
+ mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
+ mKeyguardMessageAreaControllerFactory, mLatencyTracker) {
+ @Override
+ void resetState() {
+ }
+
+ @Override
+ public void onResume(int reason) {
+ super.onResume(reason);
+ }
+ };
+ mKeyguardAbsKeyInputViewController.init();
+ reset(mKeyguardMessageAreaController); // Clear out implicit call to init.
+ }
+
+ @Test
+ public void onKeyDown_clearsSecurityMessage() {
+ ArgumentCaptor<KeyDownListener> onKeyDownListenerArgumentCaptor =
+ ArgumentCaptor.forClass(KeyDownListener.class);
+ verify(mAbsKeyInputView).setKeyDownListener(onKeyDownListenerArgumentCaptor.capture());
+ onKeyDownListenerArgumentCaptor.getValue().onKeyDown(
+ KeyEvent.KEYCODE_0, mock(KeyEvent.class));
+ verify(mKeyguardSecurityCallback).userActivity();
+ verify(mKeyguardMessageAreaController).setMessage(eq(""));
+ }
+
+ @Test
+ public void onKeyDown_noSecurityMessageInteraction() {
+ ArgumentCaptor<KeyDownListener> onKeyDownListenerArgumentCaptor =
+ ArgumentCaptor.forClass(KeyDownListener.class);
+ verify(mAbsKeyInputView).setKeyDownListener(onKeyDownListenerArgumentCaptor.capture());
+ onKeyDownListenerArgumentCaptor.getValue().onKeyDown(
+ KeyEvent.KEYCODE_UNKNOWN, mock(KeyEvent.class));
+ verifyZeroInteractions(mKeyguardSecurityCallback);
+ verifyZeroInteractions(mKeyguardMessageAreaController);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index 5999e2cdec78..e7930795c7f8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -41,11 +41,9 @@ import android.widget.FrameLayout;
import android.widget.TextClock;
import com.android.systemui.R;
-import com.android.systemui.SystemUIFactory;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.util.InjectionInflationController;
import org.junit.Before;
import org.junit.Test;
@@ -78,12 +76,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase {
when(mMockKeyguardSliceView.findViewById(R.id.keyguard_status_area))
.thenReturn(mMockKeyguardSliceView);
- InjectionInflationController inflationController = new InjectionInflationController(
- SystemUIFactory.getInstance()
- .getSysUIComponent()
- .createViewInstanceCreatorFactory());
- LayoutInflater layoutInflater = inflationController
- .injectable(LayoutInflater.from(getContext()));
+ LayoutInflater layoutInflater = LayoutInflater.from(getContext());
layoutInflater.setPrivateFactory(new LayoutInflater.Factory2() {
@Override
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
new file mode 100644
index 000000000000..a7197cca530c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
@@ -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.
+ */
+
+package com.android.keyguard;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+
+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;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class KeyguardMessageAreaControllerTest extends SysuiTestCase {
+ @Mock
+ private ConfigurationController mConfigurationController;
+ @Mock
+ private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock
+ private KeyguardMessageArea mKeyguardMessageArea;
+
+ private KeyguardMessageAreaController mMessageAreaController;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mMessageAreaController = new KeyguardMessageAreaController.Factory(
+ mKeyguardUpdateMonitor, mConfigurationController).create(mKeyguardMessageArea);
+ }
+
+ @Test
+ public void onAttachedToWindow_registersConfigurationCallback() {
+ ArgumentCaptor<ConfigurationListener> configurationListenerArgumentCaptor =
+ ArgumentCaptor.forClass(ConfigurationListener.class);
+
+ mMessageAreaController.onViewAttached();
+ verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture());
+
+ mMessageAreaController.onViewDetached();
+ verify(mConfigurationController).removeCallback(
+ eq(configurationListenerArgumentCaptor.getValue()));
+ }
+
+ @Test
+ public void onAttachedToWindow_registersKeyguardUpdateMontiorCallback() {
+ ArgumentCaptor<KeyguardUpdateMonitorCallback> keyguardUpdateMonitorCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
+
+ mMessageAreaController.onViewAttached();
+ verify(mKeyguardUpdateMonitor).registerCallback(
+ keyguardUpdateMonitorCallbackArgumentCaptor.capture());
+
+ mMessageAreaController.onViewDetached();
+ verify(mKeyguardUpdateMonitor).removeCallback(
+ eq(keyguardUpdateMonitorCallbackArgumentCaptor.getValue()));
+ }
+
+ @Test
+ public void testClearsTextField() {
+ mMessageAreaController.setMessage("");
+ verify(mKeyguardMessageArea).setMessage("");
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java
index fc7b9a4b47d1..31fb25a7a89c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.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.
@@ -11,65 +11,60 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.keyguard;
-import static junit.framework.Assert.assertEquals;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.verify;
+import static com.google.common.truth.Truth.assertThat;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
+import android.view.View;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.policy.ConfigurationController;
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)
@RunWithLooper
public class KeyguardMessageAreaTest extends SysuiTestCase {
- @Mock
- private ConfigurationController mConfigurationController;
- @Mock
- private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private KeyguardMessageArea mMessageArea;
+ private KeyguardMessageArea mKeyguardMessageArea;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mMessageArea = new KeyguardMessageArea(mContext, null, mKeyguardUpdateMonitor,
- mConfigurationController);
- waitForIdleSync();
+ mKeyguardMessageArea = new KeyguardMessageArea(mContext, null);
+ mKeyguardMessageArea.setBouncerVisible(true);
}
@Test
- public void onAttachedToWindow_registersConfigurationCallback() {
- mMessageArea.onAttachedToWindow();
- verify(mConfigurationController).addCallback(eq(mMessageArea));
-
- mMessageArea.onDetachedFromWindow();
- verify(mConfigurationController).removeCallback(eq(mMessageArea));
+ public void testShowsTextField() {
+ mKeyguardMessageArea.setVisibility(View.INVISIBLE);
+ mKeyguardMessageArea.setMessage("oobleck");
+ assertThat(mKeyguardMessageArea.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mKeyguardMessageArea.getText()).isEqualTo("oobleck");
}
@Test
- public void clearFollowedByMessage_keepsMessage() {
- mMessageArea.setMessage("");
- mMessageArea.setMessage("test");
-
- CharSequence[] messageText = new CharSequence[1];
- messageText[0] = mMessageArea.getText();
-
- assertEquals("test", messageText[0]);
+ public void testHiddenWhenBouncerHidden() {
+ mKeyguardMessageArea.setBouncerVisible(false);
+ mKeyguardMessageArea.setVisibility(View.INVISIBLE);
+ mKeyguardMessageArea.setMessage("oobleck");
+ assertThat(mKeyguardMessageArea.getVisibility()).isEqualTo(View.INVISIBLE);
+ assertThat(mKeyguardMessageArea.getText()).isEqualTo("oobleck");
}
+ @Test
+ public void testClearsTextField() {
+ mKeyguardMessageArea.setVisibility(View.VISIBLE);
+ mKeyguardMessageArea.setMessage("");
+ assertThat(mKeyguardMessageArea.getVisibility()).isEqualTo(View.INVISIBLE);
+ assertThat(mKeyguardMessageArea.getText()).isEqualTo("");
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
new file mode 100644
index 000000000000..c69ec1a254c3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -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.keyguard
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.util.LatencyTracker
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternView
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class KeyguardPatternViewControllerTest : SysuiTestCase() {
+ @Mock
+ private lateinit var mKeyguardPatternView: KeyguardPatternView
+ @Mock
+ private lateinit var mKeyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock
+ private lateinit var mSecurityMode: KeyguardSecurityModel.SecurityMode
+ @Mock
+ private lateinit var mLockPatternUtils: LockPatternUtils
+ @Mock
+ private lateinit var mKeyguardSecurityCallback: KeyguardSecurityCallback
+ @Mock
+ private lateinit var mLatencyTracker: LatencyTracker
+ @Mock
+ private lateinit
+ var mKeyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory
+ @Mock
+ private lateinit var mKeyguardMessageArea: KeyguardMessageArea
+ @Mock
+ private lateinit var mKeyguardMessageAreaController: KeyguardMessageAreaController
+ @Mock
+ private lateinit var mLockPatternView: LockPatternView
+
+ private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ `when`(mKeyguardPatternView.isAttachedToWindow).thenReturn(true)
+ `when`(mKeyguardPatternView.findViewById<KeyguardMessageArea>(R.id.keyguard_message_area))
+ .thenReturn(mKeyguardMessageArea)
+ `when`(mKeyguardPatternView.findViewById<LockPatternView>(R.id.lockPatternView))
+ .thenReturn(mLockPatternView)
+ `when`(mKeyguardMessageAreaControllerFactory.create(mKeyguardMessageArea))
+ .thenReturn(mKeyguardMessageAreaController)
+ mKeyguardPatternViewController = KeyguardPatternViewController(mKeyguardPatternView,
+ mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
+ mLatencyTracker, mKeyguardMessageAreaControllerFactory)
+ }
+
+ @Test
+ fun onPause_clearsTextField() {
+ mKeyguardPatternViewController.init()
+ mKeyguardPatternViewController.onPause()
+ verify(mKeyguardMessageAreaController).setMessage("")
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewTest.kt
deleted file mode 100644
index b4363cf215f1..000000000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewTest.kt
+++ /dev/null
@@ -1,59 +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.keyguard
-
-import androidx.test.filters.SmallTest
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.LayoutInflater
-
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.google.common.truth.Truth.assertThat
-
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
-class KeyguardPatternViewTest : SysuiTestCase() {
-
- private lateinit var mKeyguardPatternView: KeyguardPatternView
- private lateinit var mSecurityMessage: KeyguardMessageArea
-
- @Before
- fun setup() {
- val inflater = LayoutInflater.from(context)
- mDependency.injectMockDependency(KeyguardUpdateMonitor::class.java)
- mKeyguardPatternView = inflater.inflate(R.layout.keyguard_pattern_view, null)
- as KeyguardPatternView
- mSecurityMessage = KeyguardMessageArea(mContext, null,
- mock(KeyguardUpdateMonitor::class.java), mock(ConfigurationController::class.java))
- mKeyguardPatternView.mSecurityMessageDisplay = mSecurityMessage
- }
-
- @Test
- fun onPause_clearsTextField() {
- mSecurityMessage.setMessage("an old message")
- mKeyguardPatternView.onPause()
- assertThat(mSecurityMessage.text).isEqualTo("")
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
new file mode 100644
index 000000000000..4944284698a0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.keyguard;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.LatencyTracker;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+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)
+@RunWithLooper
+public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase {
+
+ @Mock
+ private KeyguardPinBasedInputView mPinBasedInputView;
+ @Mock
+ private PasswordTextView mPasswordEntry;
+ @Mock
+ private KeyguardMessageArea mKeyguardMessageArea;
+ @Mock
+ private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock
+ private SecurityMode mSecurityMode;
+ @Mock
+ private LockPatternUtils mLockPatternUtils;
+ @Mock
+ private KeyguardSecurityCallback mKeyguardSecurityCallback;
+ @Mock
+ private KeyguardMessageAreaController.Factory mKeyguardMessageAreaControllerFactory;
+ @Mock
+ private KeyguardMessageAreaController mKeyguardMessageAreaController;
+ @Mock
+ private LatencyTracker mLatencyTracker;
+ @Mock
+ private LiftToActivateListener mLiftToactivateListener;
+ @Mock
+ private View mDeleteButton;
+ @Mock
+ private View mOkButton;
+
+ private KeyguardPinBasedInputViewController mKeyguardPinViewController;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ when(mKeyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea.class)))
+ .thenReturn(mKeyguardMessageAreaController);
+ when(mPinBasedInputView.getPasswordTextViewId()).thenReturn(1);
+ when(mPinBasedInputView.findViewById(1)).thenReturn(mPasswordEntry);
+ when(mPinBasedInputView.isAttachedToWindow()).thenReturn(true);
+ when(mPinBasedInputView.findViewById(R.id.keyguard_message_area))
+ .thenReturn(mKeyguardMessageArea);
+ when(mPinBasedInputView.findViewById(R.id.delete_button))
+ .thenReturn(mDeleteButton);
+ when(mPinBasedInputView.findViewById(R.id.key_enter))
+ .thenReturn(mOkButton);
+ mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView,
+ mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
+ mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener) {
+ @Override
+ public void onResume(int reason) {
+ super.onResume(reason);
+ }
+ };
+ mKeyguardPinViewController.init();
+ }
+
+ @Test
+ public void onResume_requestsFocus() {
+ mKeyguardPinViewController.onResume(KeyguardSecurityView.SCREEN_ON);
+ verify(mPasswordEntry).requestFocus();
+ }
+}
+
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewTest.java
deleted file mode 100644
index 6666a926c68b..000000000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewTest.java
+++ /dev/null
@@ -1,79 +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.keyguard;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-public class KeyguardPinBasedInputViewTest extends SysuiTestCase {
-
- @Mock
- private PasswordTextView mPasswordEntry;
- @Mock
- private SecurityMessageDisplay mSecurityMessageDisplay;
- @InjectMocks
- private KeyguardPinBasedInputView mKeyguardPinView;
-
- @Before
- public void setup() {
- LayoutInflater inflater = LayoutInflater.from(getContext());
- mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
- mKeyguardPinView =
- (KeyguardPinBasedInputView) inflater.inflate(R.layout.keyguard_pin_view, null);
- MockitoAnnotations.initMocks(this);
- }
-
- @Test
- public void onResume_requestsFocus() {
- mKeyguardPinView.onResume(KeyguardSecurityView.SCREEN_ON);
- verify(mPasswordEntry).requestFocus();
- }
-
- @Test
- public void onKeyDown_clearsSecurityMessage() {
- mKeyguardPinView.onKeyDown(KeyEvent.KEYCODE_0, mock(KeyEvent.class));
- verify(mSecurityMessageDisplay).setMessage(eq(""));
- }
-
- @Test
- public void onKeyDown_noSecurityMessageInteraction() {
- mKeyguardPinView.onKeyDown(KeyEvent.KEYCODE_UNKNOWN, mock(KeyEvent.class));
- verifyZeroInteractions(mSecurityMessageDisplay);
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
index 559284ac0672..ae159c73b99f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
@@ -31,9 +31,7 @@ import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardDisplayManager.KeyguardPresentation;
import com.android.keyguard.dagger.KeyguardStatusViewComponent;
import com.android.systemui.R;
-import com.android.systemui.SystemUIFactory;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.util.InjectionInflationController;
import org.junit.After;
import org.junit.Before;
@@ -65,7 +63,6 @@ public class KeyguardPresentationTest extends SysuiTestCase {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
when(mMockKeyguardClockSwitch.getContext()).thenReturn(mContext);
when(mMockKeyguardSliceView.getContext()).thenReturn(mContext);
when(mMockKeyguardStatusView.getContext()).thenReturn(mContext);
@@ -77,11 +74,7 @@ public class KeyguardPresentationTest extends SysuiTestCase {
allowTestableLooperAsMainThread();
- InjectionInflationController inflationController = new InjectionInflationController(
- SystemUIFactory.getInstance()
- .getSysUIComponent()
- .createViewInstanceCreatorFactory());
- mLayoutInflater = inflationController.injectable(LayoutInflater.from(mContext));
+ mLayoutInflater = LayoutInflater.from(mContext);
mLayoutInflater.setPrivateFactory(new LayoutInflater.Factory2() {
@Override
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
new file mode 100644
index 000000000000..cdb91ecfad89
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.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.keyguard;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.WindowInsetsController;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper()
+public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
+
+ @Rule
+ public MockitoRule mRule = MockitoJUnit.rule();
+
+ @Mock
+ private KeyguardSecurityContainer mView;
+ @Mock
+ private AdminSecondaryLockScreenController.Factory mAdminSecondaryLockScreenControllerFactory;
+ @Mock
+ private AdminSecondaryLockScreenController mAdminSecondaryLockScreenController;
+ @Mock
+ private LockPatternUtils mLockPatternUtils;
+ @Mock
+ private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock
+ private KeyguardSecurityModel mKeyguardSecurityModel;
+ @Mock
+ private MetricsLogger mMetricsLogger;
+ @Mock
+ private UiEventLogger mUiEventLogger;
+ @Mock
+ private KeyguardStateController mKeyguardStateController;
+ @Mock
+ private KeyguardInputViewController mInputViewController;
+ @Mock
+ private KeyguardSecurityContainer.SecurityCallback mSecurityCallback;
+ @Mock
+ private WindowInsetsController mWindowInsetsController;
+ @Mock
+ private KeyguardSecurityViewFlipper mSecurityViewFlipper;
+ @Mock
+ private KeyguardSecurityViewFlipperController mKeyguardSecurityViewFlipperController;
+
+ private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
+
+ @Before
+ public void setup() {
+ when(mAdminSecondaryLockScreenControllerFactory.create(any(KeyguardSecurityCallback.class)))
+ .thenReturn(mAdminSecondaryLockScreenController);
+ when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController);
+
+ mKeyguardSecurityContainerController = new KeyguardSecurityContainerController(
+ mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
+ mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
+ mKeyguardStateController, mKeyguardSecurityViewFlipperController);
+
+ mKeyguardSecurityContainerController.setSecurityCallback(mSecurityCallback);
+ }
+
+ @Test
+ public void showSecurityScreen_canInflateAllModes() {
+ SecurityMode[] modes = SecurityMode.values();
+ for (SecurityMode mode : modes) {
+ when(mInputViewController.getSecurityMode()).thenReturn(mode);
+ mKeyguardSecurityContainerController.showSecurityScreen(mode);
+ if (mode == SecurityMode.Invalid) {
+ verify(mKeyguardSecurityViewFlipperController, never()).getSecurityView(
+ any(SecurityMode.class), any(KeyguardSecurityCallback.class));
+ } else {
+ verify(mKeyguardSecurityViewFlipperController).getSecurityView(
+ eq(mode), any(KeyguardSecurityCallback.class));
+ }
+ }
+ }
+
+ @Test
+ public void startDisappearAnimation_animatesKeyboard() {
+ when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
+ SecurityMode.Password);
+ when(mInputViewController.getSecurityMode()).thenReturn(
+ SecurityMode.Password);
+ when(mKeyguardSecurityViewFlipperController.getSecurityView(
+ eq(SecurityMode.Password), any(KeyguardSecurityCallback.class)))
+ .thenReturn(mInputViewController);
+ mKeyguardSecurityContainerController.showPrimarySecurityScreen(false /* turningOff */);
+
+ mKeyguardSecurityContainerController.startDisappearAnimation(null);
+ verify(mInputViewController).startDisappearAnimation(eq(null));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index a867825e223d..854be1f76722 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -19,23 +19,19 @@ package com.android.keyguard;
import static android.view.WindowInsets.Type.ime;
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.verify;
import static org.mockito.Mockito.when;
-import android.content.Context;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import android.view.LayoutInflater;
import android.view.WindowInsetsController;
import androidx.test.filters.SmallTest;
-import com.android.systemui.R;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Before;
import org.junit.Rule;
@@ -50,68 +46,26 @@ import org.mockito.junit.MockitoRule;
@TestableLooper.RunWithLooper()
public class KeyguardSecurityContainerTest extends SysuiTestCase {
- @Mock
- private KeyguardSecurityModel mKeyguardSecurityModel;
- @Mock
- private KeyguardStateController mKeyguardStateController;
- @Mock
- private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock
- private KeyguardSecurityContainer.SecurityCallback mSecurityCallback;
- @Mock
- private KeyguardSecurityView mSecurityView;
+ @Rule
+ public MockitoRule mRule = MockitoJUnit.rule();
+
@Mock
private WindowInsetsController mWindowInsetsController;
@Mock
private KeyguardSecurityViewFlipper mSecurityViewFlipper;
- @Rule
- public MockitoRule mRule = MockitoJUnit.rule();
+
private KeyguardSecurityContainer mKeyguardSecurityContainer;
@Before
public void setup() {
- mDependency.injectTestDependency(KeyguardStateController.class, mKeyguardStateController);
- mDependency.injectTestDependency(KeyguardSecurityModel.class, mKeyguardSecurityModel);
- mDependency.injectTestDependency(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor);
- mKeyguardSecurityContainer = new KeyguardSecurityContainer(getContext()) {
- @Override
- protected KeyguardSecurityView getSecurityView(
- KeyguardSecurityModel.SecurityMode securityMode) {
- return mSecurityView;
- }
- };
- mKeyguardSecurityContainer.mSecurityViewFlipper = mSecurityViewFlipper;
when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController);
- mKeyguardSecurityContainer.setSecurityCallback(mSecurityCallback);
- }
-
- @Test
- public void showSecurityScreen_canInflateAllModes() {
- Context context = getContext();
-
- for (int theme : new int[] {R.style.Theme_SystemUI, R.style.Theme_SystemUI_Light}) {
- context.setTheme(theme);
- final LayoutInflater inflater = LayoutInflater.from(context);
- KeyguardSecurityModel.SecurityMode[] modes =
- KeyguardSecurityModel.SecurityMode.values();
- for (KeyguardSecurityModel.SecurityMode mode : modes) {
- final int resId = mKeyguardSecurityContainer.getLayoutIdFor(mode);
- if (resId == 0) {
- continue;
- }
- inflater.inflate(resId, null /* root */, false /* attach */);
- }
- }
+ mKeyguardSecurityContainer = new KeyguardSecurityContainer(getContext());
+ mKeyguardSecurityContainer.mSecurityViewFlipper = mSecurityViewFlipper;
}
@Test
public void startDisappearAnimation_animatesKeyboard() {
- when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
- KeyguardSecurityModel.SecurityMode.Password);
- mKeyguardSecurityContainer.showPrimarySecurityScreen(false /* turningOff */);
-
- mKeyguardSecurityContainer.startDisappearAnimation(null);
- verify(mSecurityView).startDisappearAnimation(eq(null));
+ mKeyguardSecurityContainer.startDisappearAnimation(SecurityMode.Password);
verify(mWindowInsetsController).controlWindowInsetsAnimation(eq(ime()), anyLong(), any(),
any(), any());
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
new file mode 100644
index 000000000000..3b7f4b839853
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.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.android.keyguard;
+
+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.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.view.WindowInsetsController;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper()
+public class KeyguardSecurityViewFlipperControllerTest extends SysuiTestCase {
+
+ @Rule
+ public MockitoRule mRule = MockitoJUnit.rule();
+
+ @Mock
+ private KeyguardSecurityViewFlipper mView;
+ @Mock
+ private LayoutInflater mLayoutInflater;
+ @Mock
+ private KeyguardInputViewController.Factory mKeyguardSecurityViewControllerFactory;
+ @Mock
+ private KeyguardInputViewController mKeyguardInputViewController;
+ @Mock
+ private KeyguardInputView mInputView;
+ @Mock
+ private WindowInsetsController mWindowInsetsController;
+ @Mock
+ private KeyguardSecurityCallback mKeyguardSecurityCallback;
+
+ private KeyguardSecurityViewFlipperController mKeyguardSecurityViewFlipperController;
+
+ @Before
+ public void setup() {
+ when(mKeyguardSecurityViewControllerFactory.create(
+ any(KeyguardInputView.class), any(SecurityMode.class),
+ any(KeyguardSecurityCallback.class)))
+ .thenReturn(mKeyguardInputViewController);
+ when(mView.getWindowInsetsController()).thenReturn(mWindowInsetsController);
+
+ mKeyguardSecurityViewFlipperController = new KeyguardSecurityViewFlipperController(mView,
+ mLayoutInflater, mKeyguardSecurityViewControllerFactory);
+ }
+
+ @Test
+ public void showSecurityScreen_canInflateAllModes() {
+ SecurityMode[] modes = SecurityMode.values();
+ // Always return an invalid controller so that we're always making a new one.
+ when(mKeyguardInputViewController.getSecurityMode()).thenReturn(SecurityMode.Invalid);
+ for (SecurityMode mode : modes) {
+ reset(mLayoutInflater);
+ when(mLayoutInflater.inflate(anyInt(), eq(mView), eq(false)))
+ .thenReturn(mInputView);
+ mKeyguardSecurityViewFlipperController.getSecurityView(mode, mKeyguardSecurityCallback);
+ if (mode == SecurityMode.Invalid || mode == SecurityMode.None) {
+ verify(mLayoutInflater, never()).inflate(
+ anyInt(), any(ViewGroup.class), anyBoolean());
+ } else {
+ verify(mLayoutInflater).inflate(anyInt(), eq(mView), eq(false));
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java
index 0431704778c3..79ec4f2c553a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java
@@ -24,9 +24,7 @@ import android.testing.TestableLooper.RunWithLooper;
import android.view.LayoutInflater;
import com.android.systemui.R;
-import com.android.systemui.SystemUIFactory;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.util.InjectionInflationController;
import org.junit.Before;
import org.junit.Test;
@@ -50,13 +48,7 @@ public class KeyguardStatusViewTest extends SysuiTestCase {
@Before
public void setUp() {
allowTestableLooperAsMainThread();
- mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
- InjectionInflationController inflationController = new InjectionInflationController(
- SystemUIFactory.getInstance()
- .getSysUIComponent()
- .createViewInstanceCreatorFactory());
- LayoutInflater layoutInflater = inflationController
- .injectable(LayoutInflater.from(getContext()));
+ LayoutInflater layoutInflater = LayoutInflater.from(getContext());
mKeyguardStatusView =
(KeyguardStatusView) layoutInflater.inflate(R.layout.keyguard_status_view, null);
org.mockito.MockitoAnnotations.initMocks(this);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index 0fe381746449..86b1e61d76b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -41,7 +41,6 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.SmartReplyController;
-import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
import org.junit.After;
import org.junit.Before;
@@ -110,7 +109,6 @@ public abstract class SysuiTestCase {
// KeyguardUpdateMonitor to be created (injected).
// TODO(b/1531701009) Clean up NotificationContentView creation to prevent this
mDependency.injectMockDependency(SmartReplyController.class);
- mDependency.injectMockDependency(NotificationBlockingHelperManager.class);
}
@After
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 f1f394e70689..539b321991ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -29,11 +29,14 @@ import static org.mockito.Mockito.when;
import android.app.Instrumentation;
import android.content.Context;
import android.content.pm.ActivityInfo;
+import android.content.res.Resources;
import android.os.Handler;
import android.testing.AndroidTestingRunner;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceControl;
+import android.view.View;
+import android.view.WindowManager;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -63,15 +66,28 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
WindowMagnifierCallback mWindowMagnifierCallback;
@Mock
SurfaceControl.Transaction mTransaction;
- private Context mContext;
+ @Mock
+ private WindowManager mWindowManager;
+ private Resources mResources;
private WindowMagnificationController mWindowMagnificationController;
private Instrumentation mInstrumentation;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mContext = Mockito.spy(getContext());
+ mContext = Mockito.spy(getContext());
mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ WindowManager wm = mContext.getSystemService(WindowManager.class);
+ doAnswer(invocation ->
+ wm.getMaximumWindowMetrics()
+ ).when(mWindowManager).getMaximumWindowMetrics();
+ mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
+ doAnswer(invocation -> {
+ View view = invocation.getArgument(0);
+ WindowManager.LayoutParams lp = invocation.getArgument(1);
+ view.setLayoutParams(lp);
+ return null;
+ }).when(mWindowManager).addView(any(View.class), any(WindowManager.LayoutParams.class));
doAnswer(invocation -> {
FrameCallback callback = invocation.getArgument(0);
callback.doFrame(0);
@@ -81,6 +97,8 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
when(mTransaction.remove(any())).thenReturn(mTransaction);
when(mTransaction.setGeometry(any(), any(), any(),
anyInt())).thenReturn(mTransaction);
+ mResources = Mockito.spy(mContext.getResources());
+ when(mContext.getResources()).thenReturn(mResources);
mWindowMagnificationController = new WindowMagnificationController(mContext,
mHandler, mSfVsyncFrameProvider,
mMirrorWindowControl, mTransaction, mWindowMagnifierCallback);
@@ -150,4 +168,63 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_ORIENTATION);
});
}
+
+ @Test
+ public void onOrientationChanged_enabled_updateDisplayRotationAndLayout() {
+ final Display display = Mockito.spy(mContext.getDisplay());
+ when(display.getRotation()).thenReturn(Surface.ROTATION_90);
+ when(mContext.getDisplay()).thenReturn(display);
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
+ Float.NaN);
+ Mockito.reset(mWindowManager);
+ });
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_ORIENTATION);
+ });
+
+ assertEquals(Surface.ROTATION_90, mWindowMagnificationController.mRotation);
+ verify(mWindowManager).updateViewLayout(any(), any());
+ }
+
+ @Test
+ public void onOrientationChanged_disabled_updateDisplayRotation() {
+ final Display display = Mockito.spy(mContext.getDisplay());
+ when(display.getRotation()).thenReturn(Surface.ROTATION_90);
+ when(mContext.getDisplay()).thenReturn(display);
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_ORIENTATION);
+ });
+
+ assertEquals(Surface.ROTATION_90, mWindowMagnificationController.mRotation);
+ }
+
+
+ @Test
+ public void onDensityChanged_enabled_updateDimensionsAndLayout() {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
+ Float.NaN);
+ Mockito.reset(mWindowManager);
+ });
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
+ });
+
+ verify(mResources, atLeastOnce()).getDimensionPixelSize(anyInt());
+ verify(mWindowManager).removeView(any());
+ verify(mWindowManager).addView(any(), any());
+ }
+
+ @Test
+ public void onDensityChanged_disabled_updateDimensions() {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
+ });
+
+ verify(mResources, atLeastOnce()).getDimensionPixelSize(anyInt());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
index 8f082c15df36..ade329011b7d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
@@ -47,6 +47,7 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
import org.junit.Before;
@@ -82,6 +83,8 @@ public class AppOpsControllerTest extends SysuiTestCase {
private PackageManager mPackageManager;
@Mock(stubOnly = true)
private AudioManager mAudioManager;
+ @Mock()
+ private BroadcastDispatcher mDispatcher;
@Mock(stubOnly = true)
private AudioManager.AudioRecordingCallback mRecordingCallback;
@Mock(stubOnly = true)
@@ -120,7 +123,8 @@ public class AppOpsControllerTest extends SysuiTestCase {
mTestableLooper.getLooper(),
mDumpManager,
mFlagsCache,
- mAudioManager
+ mAudioManager,
+ mDispatcher
);
}
@@ -128,12 +132,14 @@ public class AppOpsControllerTest extends SysuiTestCase {
public void testOnlyListenForFewOps() {
mController.setListening(true);
verify(mAppOpsManager, times(1)).startWatchingActive(AppOpsControllerImpl.OPS, mController);
+ verify(mDispatcher, times(1)).registerReceiverWithHandler(eq(mController), any(), any());
}
@Test
public void testStopListening() {
mController.setListening(false);
verify(mAppOpsManager, times(1)).stopWatchingActive(mController);
+ verify(mDispatcher, times(1)).unregisterReceiver(mController);
}
@Test
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 88608ebde5b6..66656c5d251c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -22,6 +22,7 @@ import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
@@ -505,6 +506,30 @@ public class AuthControllerTest extends SysuiTestCase {
verify(mUdfpsController).onCancelAodInterrupt();
}
+ @Test
+ public void testOnFullyShown_DelegatesToUdfpsController() {
+ mAuthController.onFullyShown();
+ verify(mUdfpsController).setBouncerVisibility(eq(true));
+ }
+
+ @Test
+ public void testOnFullyHidden_DelegatesToUdfpsController() {
+ mAuthController.onFullyHidden();
+ verify(mUdfpsController).setBouncerVisibility(eq(false));
+ }
+
+ @Test
+ public void testOnStartingToShow_NeverDelegatesToUdfpsController() {
+ mAuthController.onStartingToShow();
+ verify(mUdfpsController).setBouncerVisibility(eq(true));
+ }
+
+ @Test
+ public void testOnStartingToHide_NeverDelegatesToUdfpsController() {
+ mAuthController.onStartingToHide();
+ verify(mUdfpsController, never()).setBouncerVisibility(anyBoolean());
+ }
+
// Helpers
private void showDialog(int authenticators, int biometricModality) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
new file mode 100644
index 000000000000..9b9f840e5383
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.biometrics;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.WindowManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.settings.FakeSettings;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class UdfpsControllerTest extends SysuiTestCase {
+
+ @Rule
+ public MockitoRule rule = MockitoJUnit.rule();
+
+ // Unit under test
+ private UdfpsController mUdfpsController;
+
+ // Dependencies
+ @Mock
+ private Resources mResources;
+ @Mock
+ private LayoutInflater mLayoutInflater;
+ @Mock
+ private FingerprintManager mFingerprintManager;
+ @Mock
+ private PowerManager mPowerManager;
+ @Mock
+ private WindowManager mWindowManager;
+ @Mock
+ private StatusBarStateController mStatusBarStateController;
+ private FakeSettings mSystemSettings;
+ private FakeExecutor mFgExecutor;
+
+ // Stuff for configuring mocks
+ @Mock
+ private UdfpsView mUdfpsView;
+ @Mock
+ private TypedArray mBrightnessValues;
+ @Mock
+ private TypedArray mBrightnessBacklight;
+
+ // Capture listeners so that they can be used to send events
+ @Captor private ArgumentCaptor<IUdfpsOverlayController> mOverlayCaptor;
+ private IUdfpsOverlayController mOverlayController;
+ @Captor private ArgumentCaptor<UdfpsView.OnTouchListener> mTouchListenerCaptor;
+
+ @Before
+ public void setUp() {
+ setUpResources();
+ when(mLayoutInflater.inflate(R.layout.udfps_view, null, false)).thenReturn(mUdfpsView);
+ mSystemSettings = new FakeSettings();
+ mFgExecutor = new FakeExecutor(new FakeSystemClock());
+ mUdfpsController = new UdfpsController(
+ mContext,
+ mResources,
+ mLayoutInflater,
+ mFingerprintManager,
+ mPowerManager,
+ mWindowManager,
+ mSystemSettings,
+ mStatusBarStateController,
+ mFgExecutor);
+ verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
+ mOverlayController = mOverlayCaptor.getValue();
+ }
+
+ private void setUpResources() {
+ when(mBrightnessValues.length()).thenReturn(2);
+ when(mBrightnessValues.getFloat(0, PowerManager.BRIGHTNESS_OFF_FLOAT)).thenReturn(1f);
+ when(mBrightnessValues.getFloat(1, PowerManager.BRIGHTNESS_OFF_FLOAT)).thenReturn(2f);
+ when(mResources.obtainTypedArray(com.android.internal.R.array.config_screenBrightnessNits))
+ .thenReturn(mBrightnessValues);
+ when(mBrightnessBacklight.length()).thenReturn(2);
+ when(mBrightnessBacklight.getFloat(0, PowerManager.BRIGHTNESS_OFF_FLOAT)).thenReturn(1f);
+ when(mBrightnessBacklight.getFloat(1, PowerManager.BRIGHTNESS_OFF_FLOAT)).thenReturn(2f);
+ when(mResources.obtainTypedArray(
+ com.android.internal.R.array.config_autoBrightnessDisplayValuesNits))
+ .thenReturn(mBrightnessBacklight);
+ when(mResources.getIntArray(com.android.internal.R.array.config_screenBrightnessBacklight))
+ .thenReturn(new int[]{1, 2});
+ }
+
+ @Test
+ public void dozeTimeTick() {
+ mUdfpsController.dozeTimeTick();
+ verify(mUdfpsView).dozeTimeTick();
+ }
+
+ @Test
+ public void showUdfpsOverlay_addsViewToWindow() throws RemoteException {
+ mOverlayController.showUdfpsOverlay();
+ mFgExecutor.runAllReady();
+ verify(mWindowManager).addView(eq(mUdfpsView), any());
+ }
+
+ @Test
+ public void hideUdfpsOverlay_removesViewFromWindow() throws RemoteException {
+ mOverlayController.showUdfpsOverlay();
+ mOverlayController.hideUdfpsOverlay();
+ mFgExecutor.runAllReady();
+ verify(mWindowManager).removeView(eq(mUdfpsView));
+ }
+
+ @Test
+ public void showUdfpsOverlay_bouncerShowing() throws RemoteException {
+ // GIVEN that the bouncer is showing
+ mUdfpsController.setBouncerVisibility(/* isShowing */ true);
+ // WHEN a request to show the overlay is received
+ mOverlayController.showUdfpsOverlay();
+ mFgExecutor.runAllReady();
+ // THEN the overlay is not attached
+ verify(mWindowManager, never()).addView(eq(mUdfpsView), any());
+ }
+
+ @Test
+ public void setBouncerVisibility_overlayDetached() throws RemoteException {
+ // GIVEN that the overlay has been requested
+ mOverlayController.showUdfpsOverlay();
+ // WHEN the bouncer becomes visible
+ mUdfpsController.setBouncerVisibility(/* isShowing */ true);
+ mFgExecutor.runAllReady();
+ // THEN the overlay is detached
+ verify(mWindowManager).removeView(eq(mUdfpsView));
+ }
+
+ @Test
+ public void setBouncerVisibility_overlayAttached() throws RemoteException {
+ // GIVEN that the bouncer is visible
+ mUdfpsController.setBouncerVisibility(/* isShowing */ true);
+ // AND the overlay has been requested
+ mOverlayController.showUdfpsOverlay();
+ // WHEN the bouncer is closed
+ mUdfpsController.setBouncerVisibility(/* isShowing */ false);
+ mFgExecutor.runAllReady();
+ // THEN the overlay is attached
+ verify(mWindowManager).addView(eq(mUdfpsView), any());
+ }
+
+ @Test
+ public void fingerDown() throws RemoteException {
+ // Configure UdfpsView to accept the ACTION_DOWN event
+ when(mUdfpsView.isScrimShowing()).thenReturn(false);
+ when(mUdfpsView.isValidTouch(anyFloat(), anyFloat(), anyFloat())).thenReturn(true);
+
+ // GIVEN that the overlay is showing
+ mOverlayController.showUdfpsOverlay();
+ mFgExecutor.runAllReady();
+ // WHEN ACTION_DOWN is received
+ verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+ MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+ 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));
+ // AND the scrim and dot is shown
+ verify(mUdfpsView).showScrimAndDot();
+ }
+
+ @Test
+ public void aodInterrupt() throws RemoteException {
+ // GIVEN that the overlay is showing
+ mOverlayController.showUdfpsOverlay();
+ 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());
+ // AND the scrim and dot is shown
+ verify(mUdfpsView).showScrimAndDot();
+ }
+
+ @Test
+ public void cancelAodInterrupt() throws RemoteException {
+ // GIVEN AOD interrupt
+ mOverlayController.showUdfpsOverlay();
+ mFgExecutor.runAllReady();
+ mUdfpsController.onAodInterrupt(0, 0);
+ // WHEN it is cancelled
+ mUdfpsController.onCancelAodInterrupt();
+ // THEN the scrim and dot is hidden
+ verify(mUdfpsView).hideScrimAndDot();
+ }
+
+ @Test
+ public void aodInterruptTimeout() throws RemoteException {
+ // GIVEN AOD interrupt
+ mOverlayController.showUdfpsOverlay();
+ mFgExecutor.runAllReady();
+ mUdfpsController.onAodInterrupt(0, 0);
+ // WHEN it times out
+ mFgExecutor.advanceClockToNext();
+ mFgExecutor.runAllReady();
+ // THEN the scrim and dot is hidden
+ verify(mUdfpsView).hideScrimAndDot();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
index b85b1c237f6b..be0865d1b3e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -115,8 +115,6 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
@Test
public void testAod_usesLightSensor() {
- mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
- mScreen.transitionTo(INITIALIZED, DOZE_AOD);
mScreen.onScreenState(Display.STATE_DOZE);
waitForSensorManager();
@@ -127,8 +125,6 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
@Test
public void testAod_usesDebugValue() throws Exception {
- mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
- mScreen.transitionTo(INITIALIZED, DOZE_AOD);
mScreen.onScreenState(Display.STATE_DOZE);
waitForSensorManager();
@@ -181,14 +177,13 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
}
@Test
- public void testDozingAfterPulsing_pausesLightSensor() throws Exception {
+ public void testScreenOffAfterPulsing_pausesLightSensor() throws Exception {
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE);
mScreen.transitionTo(DOZE, DOZE_REQUEST_PULSE);
mScreen.transitionTo(DOZE_REQUEST_PULSE, DOZE_PULSING);
mScreen.transitionTo(DOZE_PULSING, DOZE_PULSE_DONE);
mScreen.transitionTo(DOZE_PULSE_DONE, DOZE);
- mScreen.onScreenState(Display.STATE_DOZE);
waitForSensorManager();
mSensor.sendSensorEvent(1);
@@ -197,6 +192,18 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
}
@Test
+ public void testOnScreenStateSetBeforeTransition_stillRegistersSensor() {
+ mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+ mScreen.onScreenState(Display.STATE_DOZE);
+ mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+ waitForSensorManager();
+
+ mSensor.sendSensorEvent(1);
+
+ assertEquals(1, mServiceFake.screenBrightness);
+ }
+
+ @Test
public void testNullSensor() throws Exception {
mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
Optional.empty() /* sensor */, mDozeHost, null /* handler */,
@@ -206,12 +213,15 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
mScreen.transitionTo(DOZE_AOD, DOZE_AOD_PAUSING);
mScreen.transitionTo(DOZE_AOD_PAUSING, DOZE_AOD_PAUSED);
+ mScreen.onScreenState(Display.STATE_DOZE);
+ mScreen.onScreenState(Display.STATE_OFF);
}
@Test
public void testNoBrightnessDeliveredAfterFinish() throws Exception {
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+ mScreen.onScreenState(Display.STATE_DOZE);
mScreen.transitionTo(DOZE_AOD, FINISH);
waitForSensorManager();
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/pip/PipAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
index f0066ba4f66a..6f5cbe21be75 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
@@ -81,7 +81,7 @@ public class PipAnimationControllerTest extends SysuiTestCase {
@Test
public void getAnimator_withBounds_returnBoundsAnimator() {
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
- .getAnimator(mLeash, new Rect(), new Rect(), null);
+ .getAnimator(mLeash, new Rect(), new Rect(), null, TRANSITION_DIRECTION_TO_PIP);
assertEquals("Expect ANIM_TYPE_BOUNDS animation",
animator.getAnimationType(), PipAnimationController.ANIM_TYPE_BOUNDS);
@@ -93,12 +93,12 @@ public class PipAnimationControllerTest extends SysuiTestCase {
final Rect endValue1 = new Rect(100, 100, 200, 200);
final Rect endValue2 = new Rect(200, 200, 300, 300);
final PipAnimationController.PipTransitionAnimator oldAnimator = mPipAnimationController
- .getAnimator(mLeash, startValue, endValue1, null);
+ .getAnimator(mLeash, startValue, endValue1, null, TRANSITION_DIRECTION_TO_PIP);
oldAnimator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new);
oldAnimator.start();
final PipAnimationController.PipTransitionAnimator newAnimator = mPipAnimationController
- .getAnimator(mLeash, startValue, endValue2, null);
+ .getAnimator(mLeash, startValue, endValue2, null, TRANSITION_DIRECTION_TO_PIP);
assertEquals("getAnimator with same type returns same animator",
oldAnimator, newAnimator);
@@ -128,7 +128,7 @@ public class PipAnimationControllerTest extends SysuiTestCase {
final Rect endValue1 = new Rect(100, 100, 200, 200);
final Rect endValue2 = new Rect(200, 200, 300, 300);
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
- .getAnimator(mLeash, startValue, endValue1, null);
+ .getAnimator(mLeash, startValue, endValue1, null, TRANSITION_DIRECTION_TO_PIP);
animator.updateEndValue(endValue2);
@@ -140,7 +140,7 @@ public class PipAnimationControllerTest extends SysuiTestCase {
final Rect startValue = new Rect(0, 0, 100, 100);
final Rect endValue = new Rect(100, 100, 200, 200);
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
- .getAnimator(mLeash, startValue, endValue, null);
+ .getAnimator(mLeash, startValue, endValue, null, TRANSITION_DIRECTION_TO_PIP);
animator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new);
animator.setPipAnimationCallback(mPipAnimationCallback);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipControllerTest.java
new file mode 100644
index 000000000000..b04349521205
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipControllerTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.pip.phone;
+
+import static 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.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.system.ActivityManagerWrapper;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.util.FloatingContentCoordinator;
+import com.android.wm.shell.common.DisplayController;
+
+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 SysuiTestCase {
+ private PipController mPipController;
+ private TestableContext mSpiedContext;
+
+ @Mock private ActivityManagerWrapper mMockActivityManagerWrapper;
+ @Mock private BroadcastDispatcher mMockBroadcastDispatcher;
+ @Mock private ConfigurationController mMockConfigurationController;
+ @Mock private DeviceConfigProxy mMockDeviceConfigProxy;
+ @Mock private DisplayController mMockdDisplayController;
+ @Mock private FloatingContentCoordinator mMockFloatingContentCoordinator;
+ @Mock private PackageManager mPackageManager;
+ @Mock private PipBoundsHandler mMockPipBoundsHandler;
+ @Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper;
+ @Mock private PipTaskOrganizer mMockPipTaskOrganizer;
+ @Mock private PipUiEventLogger mPipUiEventLogger;
+ @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);
+
+ mPipController = new PipController(mSpiedContext, mMockBroadcastDispatcher,
+ mMockConfigurationController, mMockDeviceConfigProxy, mMockdDisplayController,
+ mMockFloatingContentCoordinator, mMockSysUiState, mMockPipBoundsHandler,
+ mMockPipSurfaceTransactionHelper, mMockPipTaskOrganizer, mPipUiEventLogger);
+ }
+
+ @Test
+ public void testNonPipDevice_shouldNotRegisterTaskStackListener() {
+ verify(mMockActivityManagerWrapper, never()).registerTaskStackListener(any());
+ }
+
+ @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());
+ }
+
+ @Test
+ public void testNonPipDevice_shouldNotAddCallback() {
+ verify(mMockConfigurationController, never()).addCallback(any());
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTaskOrganizerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTaskOrganizerTest.java
new file mode 100644
index 000000000000..b1a7df83fa9f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTaskOrganizerTest.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 com.android.systemui.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.systemui.SysuiTestCase;
+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.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+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 SysuiTestCase {
+ 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;
+
+ @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);
+
+ mSpiedPipTaskOrganizer = spy(new PipTaskOrganizer(mSpiedContext, 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/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
index 53ef86660867..2ef7c65acb0b 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
@@ -34,6 +34,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.Manifest;
+import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -47,8 +48,11 @@ import android.util.ArraySet;
import androidx.test.filters.SmallTest;
+import com.android.internal.logging.InstanceId;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+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.util.concurrency.FakeExecutor;
@@ -68,6 +72,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.Executor;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -116,11 +121,9 @@ public class TileQueryHelperTest extends SysuiTestCase {
doAnswer(invocation -> {
String spec = (String) invocation.getArguments()[0];
if (FACTORY_TILES.contains(spec)) {
- QSTile m = mock(QSTile.class);
- when(m.isAvailable()).thenReturn(true);
- when(m.getTileSpec()).thenReturn(spec);
- when(m.getState()).thenReturn(mState);
- return m;
+ FakeQSTile tile = new FakeQSTile(mBgExecutor, mMainExecutor);
+ tile.setState(mState);
+ return tile;
} else {
return null;
}
@@ -292,4 +295,132 @@ public class TileQueryHelperTest extends SysuiTestCase {
verifier.verify(t).setTileSpec("hotspot");
verifier.verify(t).destroy();
}
+
+ private static class FakeQSTile implements QSTile {
+
+ private String mSpec = "";
+ private List<Callback> mCallbacks = new ArrayList<>();
+ private boolean mRefreshed;
+ private boolean mListening;
+ private State mState = new State();
+ private final Executor mBgExecutor;
+ private final Executor mMainExecutor;
+
+ FakeQSTile(Executor bgExecutor, Executor mainExecutor) {
+ mBgExecutor = bgExecutor;
+ mMainExecutor = mainExecutor;
+ }
+
+ @Override
+ public String getTileSpec() {
+ return mSpec;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+
+ @Override
+ public void setTileSpec(String tileSpec) {
+ mSpec = tileSpec;
+ }
+
+ public void setState(State state) {
+ mState = state;
+ notifyChangedState(mState);
+ }
+
+ @Override
+ public void refreshState() {
+ mBgExecutor.execute(() -> {
+ mRefreshed = true;
+ notifyChangedState(mState);
+ });
+ }
+
+ private void notifyChangedState(State state) {
+ List<Callback> callbacks = new ArrayList<>(mCallbacks);
+ callbacks.forEach(callback -> callback.onStateChanged(state));
+ }
+
+ @Override
+ public void addCallback(Callback callback) {
+ mCallbacks.add(callback);
+ }
+
+ @Override
+ public void removeCallback(Callback callback) {
+ mCallbacks.remove(callback);
+ }
+
+ @Override
+ public void removeCallbacks() {
+ mCallbacks.clear();
+ }
+
+ @Override
+ public void setListening(Object client, boolean listening) {
+ if (listening) {
+ mMainExecutor.execute(() -> {
+ mListening = true;
+ refreshState();
+ });
+ }
+ }
+
+ @Override
+ public CharSequence getTileLabel() {
+ return mSpec;
+ }
+
+ @Override
+ public State getState() {
+ return mState;
+ }
+
+ @Override
+ public boolean isTileReady() {
+ return mListening && mRefreshed;
+ }
+
+ @Override
+ public QSIconView createTileView(Context context) {
+ return null;
+ }
+
+ @Override
+ public void click() {}
+
+ @Override
+ public void secondaryClick() {}
+
+ @Override
+ public void longClick() {}
+
+ @Override
+ public void userSwitch(int currentUser) {}
+
+ @Override
+ public int getMetricsCategory() {
+ return 0;
+ }
+
+ @Override
+ public InstanceId getInstanceId() {
+ return null;
+ }
+
+ @Override
+ public void setDetailListening(boolean show) {}
+
+ @Override
+ public void destroy() {}
+
+
+ @Override
+ public DetailAdapter getDetailAdapter() {
+ return null;
+ }
+ }
}
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..727f91c589df
--- /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.pip.Pip;
+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.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/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index f29b46c73e4d..8f1d71c80e2d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -54,7 +54,6 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -71,7 +70,6 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
boolean mHeadsUpAnimatingAway = false;
@Rule public MockitoRule mockito = MockitoJUnit.rule();
- @Mock private NotificationBlockingHelperManager mBlockingHelperManager;
@Before
public void setUp() throws Exception {
@@ -83,9 +81,6 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
mGroupRow = mNotificationTestHelper.createGroup();
mGroupRow.setHeadsUpAnimatingAwayListener(
animatingAway -> mHeadsUpAnimatingAway = animatingAway);
- mDependency.injectTestDependency(
- NotificationBlockingHelperManager.class,
- mBlockingHelperManager);
}
@Test
@@ -257,30 +252,6 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
}
@Test
- public void testPerformDismissWithBlockingHelper_falseWhenBlockingHelperIsntShown() {
- when(mBlockingHelperManager.perhapsShowBlockingHelper(
- eq(mGroupRow), any(NotificationMenuRowPlugin.class))).thenReturn(false);
-
- assertFalse(
- mGroupRow.performDismissWithBlockingHelper(false /* fromAccessibility */));
- }
-
- @Test
- public void testPerformDismissWithBlockingHelper_doesntPerformOnGroupSummary() {
- ExpandableNotificationRow childRow = mGroupRow.getChildrenContainer().getViewAtPosition(0);
- when(mBlockingHelperManager.perhapsShowBlockingHelper(eq(childRow), any(NotificationMenuRowPlugin.class)))
- .thenReturn(true);
-
- assertTrue(
- childRow.performDismissWithBlockingHelper(false /* fromAccessibility */));
-
- verify(mBlockingHelperManager, times(1))
- .perhapsShowBlockingHelper(eq(childRow), any(NotificationMenuRowPlugin.class));
- verify(mBlockingHelperManager, times(0))
- .perhapsShowBlockingHelper(eq(mGroupRow), any(NotificationMenuRowPlugin.class));
- }
-
- @Test
public void testIsBlockingHelperShowing_isCorrectlyUpdated() {
mGroupRow.setBlockingHelperShowing(true);
assertTrue(mGroupRow.isBlockingHelperShowing());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 0d283ce03960..bb85cec5c099 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -3,7 +3,10 @@
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -31,7 +34,6 @@ import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -56,15 +58,14 @@ import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.FooterView;
-import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.KeyguardBypassEnabledProvider;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
import org.junit.After;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -88,7 +89,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
@Rule public MockitoRule mockito = MockitoJUnit.rule();
@Mock private StatusBar mBar;
@Mock private SysuiStatusBarStateController mBarState;
- @Mock private NotificationBlockingHelperManager mBlockingHelperManager;
@Mock private NotificationGroupManagerLegacy mGroupMembershipManger;
@Mock private NotificationGroupManagerLegacy mGroupExpansionManager;
@Mock private ExpandHelper mExpandHelper;
@@ -117,10 +117,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
1, UserHandle.USER_CURRENT);
// Inject dependencies before initializing the layout
- mDependency.injectMockDependency(VisualStabilityManager.class);
- mDependency.injectTestDependency(
- NotificationBlockingHelperManager.class,
- mBlockingHelperManager);
mDependency.injectTestDependency(SysuiStatusBarStateController.class, mBarState);
mDependency.injectMockDependency(ShadeController.class);
@@ -217,12 +213,20 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
@Test
@UiThreadTest
public void testSetExpandedHeight_blockingHelperManagerReceivedCallbacks() {
- mStackScroller.setExpandedHeight(0f);
- verify(mBlockingHelperManager).setNotificationShadeExpanded(0f);
- reset(mBlockingHelperManager);
+ final float expectedHeight[] = {0f};
+ final float expectedAppear[] = {0f};
+
+ mStackScroller.addOnExpandedHeightChangedListener((height, appear) -> {
+ Assert.assertEquals(expectedHeight[0], height, 0);
+ Assert.assertEquals(expectedAppear[0], appear, .1);
+ });
+ expectedHeight[0] = 1f;
+ expectedAppear[0] = 1f;
+ mStackScroller.setExpandedHeight(expectedHeight[0]);
- mStackScroller.setExpandedHeight(100f);
- verify(mBlockingHelperManager).setNotificationShadeExpanded(100f);
+ expectedHeight[0] = 100f;
+ expectedAppear[0] = 0f;
+ mStackScroller.setExpandedHeight(expectedHeight[0]);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java
index d83417638cc5..01d49c269fb2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java
@@ -60,6 +60,7 @@ 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.legacy.NotificationGroupManagerLegacy;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ForegroundServiceDungeonView;
@@ -68,6 +69,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScrimController;
+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;
@@ -105,7 +107,6 @@ public class NotificationStackScrollerControllerTest extends SysuiTestCase {
@Mock private NotificationLockscreenUserManager mNotificationLockscreenUserManager;
@Mock private MetricsLogger mMetricsLogger;
@Mock private FalsingManager mFalsingManager;
- @Mock private NotificationSectionsManager mNotificationSectionsManager;
@Mock private Resources mResources;
@Mock(answer = Answers.RETURNS_SELF)
private NotificationSwipeHelper.Builder mNotificationSwipeHelperBuilder;
@@ -126,6 +127,8 @@ public class NotificationStackScrollerControllerTest extends SysuiTestCase {
@Mock private LayoutInflater mLayoutInflater;
@Mock private NotificationRemoteInputManager mRemoteInputManager;
@Mock private RemoteInputController mRemoteInputController;
+ @Mock private VisualStabilityManager mVisualStabilityManager;
+ @Mock private ShadeController mShadeController;
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
@@ -158,7 +161,6 @@ public class NotificationStackScrollerControllerTest extends SysuiTestCase {
mNotificationLockscreenUserManager,
mMetricsLogger,
mFalsingManager,
- mNotificationSectionsManager,
mResources,
mNotificationSwipeHelperBuilder,
mStatusBar,
@@ -175,7 +177,9 @@ public class NotificationStackScrollerControllerTest extends SysuiTestCase {
mFgFeatureController,
mFgServicesSectionController,
mLayoutInflater,
- mRemoteInputManager
+ mRemoteInputManager,
+ mVisualStabilityManager,
+ mShadeController
);
when(mNotificationStackScrollLayout.isAttachedToWindow()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
index 1b05ad7f8b5b..4fb45ec18178 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
@@ -50,6 +50,7 @@ import com.android.keyguard.ViewMediatorCallback;
import com.android.keyguard.dagger.KeyguardBouncerComponent;
import com.android.systemui.DejankUtils;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.AuthController;
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.plugins.FalsingManager;
@@ -78,6 +79,8 @@ public class KeyguardBouncerTest extends SysuiTestCase {
@Mock
private DismissCallbackRegistry mDismissCallbackRegistry;
@Mock
+ private AuthController mAuthController;
+ @Mock
private KeyguardHostViewController mKeyguardHostViewController;
@Mock
private KeyguardBouncer.BouncerExpansionCallback mExpansionCallback;
@@ -128,7 +131,7 @@ public class KeyguardBouncerTest extends SysuiTestCase {
final ViewGroup container = new FrameLayout(getContext());
mBouncer = new KeyguardBouncer.Factory(getContext(), mViewMediatorCallback,
- mDismissCallbackRegistry, mFalsingManager,
+ mDismissCallbackRegistry, mFalsingManager, mAuthController,
mKeyguardStateController, mKeyguardUpdateMonitor,
mKeyguardBypassController, mHandler, mKeyguardSecurityModel,
mKeyguardBouncerComponentFactory)
@@ -207,6 +210,12 @@ public class KeyguardBouncerTest extends SysuiTestCase {
}
@Test
+ public void testShow_notifiesAuthControllerStartingToShow() {
+ mBouncer.show(/* resetSecuritySelection */ false);
+ verify(mAuthController).onStartingToShow();
+ }
+
+ @Test
public void testSetExpansion_notifiesFalsingManager() {
mBouncer.ensureView();
mBouncer.setExpansion(0.5f);
@@ -237,6 +246,38 @@ public class KeyguardBouncerTest extends SysuiTestCase {
}
@Test
+ public void testSetExpansion_notifiesAuthControllerFullyShown() {
+ mBouncer.ensureView();
+ mBouncer.setExpansion(0.1f);
+ mBouncer.setExpansion(0f);
+ verify(mAuthController).onFullyShown();
+ }
+
+ @Test
+ public void testSetExpansion_notifiesAuthControllerStartingToHide() {
+ mBouncer.ensureView();
+ mBouncer.setExpansion(0f);
+ mBouncer.setExpansion(0.1f);
+ verify(mAuthController).onStartingToHide();
+ }
+
+ @Test
+ public void testSetExpansion_notifiesAuthControllerFullyHidden() {
+ mBouncer.ensureView();
+ mBouncer.setExpansion(0.9f);
+ mBouncer.setExpansion(1f);
+ verify(mAuthController).onFullyHidden();
+ }
+
+ @Test
+ public void testSetExpansion_negativeAuthControllerStartingToShow() {
+ mBouncer.ensureView();
+ mBouncer.setExpansion(1f);
+ mBouncer.setExpansion(0.9f);
+ verify(mAuthController, never()).onStartingToShow();
+ }
+
+ @Test
public void testHide_notifiesFalsingManager() {
mBouncer.hide(false);
verify(mFalsingManager).onBouncerHidden();
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..280423fca202 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;
@@ -68,6 +73,7 @@ public class WMShellTest extends SysuiTestCase {
@Mock OneHanded mOneHanded;
@Mock ShellTaskOrganizer mTaskOrganizer;
@Mock ProtoTracer mProtoTracer;
+ @Mock PackageManager mMockPackageManager;
@Before
public void setUp() {
@@ -89,8 +95,23 @@ 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() {
+ TestableContext spiedContext = spy(mContext);
+ when(mMockPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)).thenReturn(false);
+ when(spiedContext.getPackageManager()).thenReturn(mMockPackageManager);
+
+ final WMShell nonPipWMShell = new WMShell(spiedContext, mCommandQueue,
+ mKeyguardUpdateMonitor,
+ mActivityManagerWrapper, mDisplayImeController, mNavigationModeController,
+ mScreenLifecycle, mSysUiState, Optional.of(mPip), Optional.of(mSplitScreen),
+ Optional.of(mOneHanded), mTaskOrganizer, mProtoTracer);
+ nonPipWMShell.initPip(mPip);
+
+ verify(mCommandQueue, never()).addCallback(any());
}
@Test
@@ -108,8 +129,7 @@ public class WMShellTest extends SysuiTestCase {
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));
diff --git a/packages/Tethering/apex/Android.bp b/packages/Tethering/apex/Android.bp
index 67097a79e5c0..05243749f765 100644
--- a/packages/Tethering/apex/Android.bp
+++ b/packages/Tethering/apex/Android.bp
@@ -19,6 +19,7 @@ apex {
updatable: true,
min_sdk_version: "current",
java_libs: ["framework-tethering"],
+ bpfs: ["offload.o"],
apps: ["Tethering"],
manifest: "manifest.json",
key: "com.android.tethering.key",
diff --git a/packages/Tethering/bpf_progs/Android.bp b/packages/Tethering/bpf_progs/Android.bp
new file mode 100644
index 000000000000..d54f86148665
--- /dev/null
+++ b/packages/Tethering/bpf_progs/Android.bp
@@ -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.
+//
+
+//
+// bpf kernel programs
+//
+bpf {
+ name: "offload.o",
+ srcs: ["offload.c"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ include_dirs: [
+ // TODO: get rid of system/netd.
+ "system/netd/bpf_progs", // for bpf_net_helpers.h
+ "system/netd/libnetdbpf/include", // for bpf_shared.h
+ "system/netd/libnetdutils/include", // for UidConstants.h
+ ],
+}
diff --git a/packages/Tethering/bpf_progs/offload.c b/packages/Tethering/bpf_progs/offload.c
new file mode 100644
index 000000000000..cc5af3127b02
--- /dev/null
+++ b/packages/Tethering/bpf_progs/offload.c
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 <linux/if.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/pkt_cls.h>
+#include <linux/tcp.h>
+
+#include "bpf_helpers.h"
+#include "bpf_net_helpers.h"
+#include "netdbpf/bpf_shared.h"
+
+DEFINE_BPF_MAP_GRW(tether_ingress_map, HASH, TetherIngressKey, TetherIngressValue, 64,
+ AID_NETWORK_STACK)
+
+// Tethering stats, indexed by upstream interface.
+DEFINE_BPF_MAP_GRW(tether_stats_map, HASH, uint32_t, TetherStatsValue, 16, AID_NETWORK_STACK)
+
+// Tethering data limit, indexed by upstream interface.
+// (tethering allowed when stats[iif].rxBytes + stats[iif].txBytes < limit[iif])
+DEFINE_BPF_MAP_GRW(tether_limit_map, HASH, uint32_t, uint64_t, 16, AID_NETWORK_STACK)
+
+static inline __always_inline int do_forward(struct __sk_buff* skb, bool is_ethernet) {
+ int l2_header_size = is_ethernet ? sizeof(struct ethhdr) : 0;
+ void* data = (void*)(long)skb->data;
+ const void* data_end = (void*)(long)skb->data_end;
+ struct ethhdr* eth = is_ethernet ? data : NULL; // used iff is_ethernet
+ struct ipv6hdr* ip6 = is_ethernet ? (void*)(eth + 1) : data;
+
+ // Must be meta-ethernet IPv6 frame
+ if (skb->protocol != htons(ETH_P_IPV6)) return TC_ACT_OK;
+
+ // Must have (ethernet and) ipv6 header
+ if (data + l2_header_size + sizeof(*ip6) > data_end) return TC_ACT_OK;
+
+ // Ethertype - if present - must be IPv6
+ if (is_ethernet && (eth->h_proto != htons(ETH_P_IPV6))) return TC_ACT_OK;
+
+ // IP version must be 6
+ if (ip6->version != 6) return TC_ACT_OK;
+
+ // Cannot decrement during forward if already zero or would be zero,
+ // Let the kernel's stack handle these cases and generate appropriate ICMP errors.
+ if (ip6->hop_limit <= 1) return TC_ACT_OK;
+
+ // Protect against forwarding packets sourced from ::1 or fe80::/64 or other weirdness.
+ __be32 src32 = ip6->saddr.s6_addr32[0];
+ if (src32 != htonl(0x0064ff9b) && // 64:ff9b:/32 incl. XLAT464 WKP
+ (src32 & htonl(0xe0000000)) != htonl(0x20000000)) // 2000::/3 Global Unicast
+ return TC_ACT_OK;
+
+ TetherIngressKey k = {
+ .iif = skb->ifindex,
+ .neigh6 = ip6->daddr,
+ };
+
+ TetherIngressValue* v = bpf_tether_ingress_map_lookup_elem(&k);
+
+ // If we don't find any offload information then simply let the core stack handle it...
+ if (!v) return TC_ACT_OK;
+
+ uint32_t stat_and_limit_k = skb->ifindex;
+
+ TetherStatsValue* stat_v = bpf_tether_stats_map_lookup_elem(&stat_and_limit_k);
+
+ // If we don't have anywhere to put stats, then abort...
+ if (!stat_v) return TC_ACT_OK;
+
+ uint64_t* limit_v = bpf_tether_limit_map_lookup_elem(&stat_and_limit_k);
+
+ // If we don't have a limit, then abort...
+ if (!limit_v) return TC_ACT_OK;
+
+ // Required IPv6 minimum mtu is 1280, below that not clear what we should do, abort...
+ const int pmtu = v->pmtu;
+ if (pmtu < IPV6_MIN_MTU) return TC_ACT_OK;
+
+ // Approximate handling of TCP/IPv6 overhead for incoming LRO/GRO packets: default
+ // outbound path mtu of 1500 is not necessarily correct, but worst case we simply
+ // undercount, which is still better then not accounting for this overhead at all.
+ // Note: this really shouldn't be device/path mtu at all, but rather should be
+ // derived from this particular connection's mss (ie. from gro segment size).
+ // This would require a much newer kernel with newer ebpf accessors.
+ // (This is also blindly assuming 12 bytes of tcp timestamp option in tcp header)
+ uint64_t packets = 1;
+ uint64_t bytes = skb->len;
+ if (bytes > pmtu) {
+ const int tcp_overhead = sizeof(struct ipv6hdr) + sizeof(struct tcphdr) + 12;
+ const int mss = pmtu - tcp_overhead;
+ const uint64_t payload = bytes - tcp_overhead;
+ packets = (payload + mss - 1) / mss;
+ bytes = tcp_overhead * packets + payload;
+ }
+
+ // Are we past the limit? If so, then abort...
+ // Note: will not overflow since u64 is 936 years even at 5Gbps.
+ // Do not drop here. Offload is just that, whenever we fail to handle
+ // a packet we let the core stack deal with things.
+ // (The core stack needs to handle limits correctly anyway,
+ // since we don't offload all traffic in both directions)
+ if (stat_v->rxBytes + stat_v->txBytes + bytes > *limit_v) return TC_ACT_OK;
+
+ if (!is_ethernet) {
+ is_ethernet = true;
+ l2_header_size = sizeof(struct ethhdr);
+ // Try to inject an ethernet header, and simply return if we fail
+ if (bpf_skb_change_head(skb, l2_header_size, /*flags*/ 0)) {
+ __sync_fetch_and_add(&stat_v->rxErrors, 1);
+ return TC_ACT_OK;
+ }
+
+ // bpf_skb_change_head() invalidates all pointers - reload them
+ data = (void*)(long)skb->data;
+ data_end = (void*)(long)skb->data_end;
+ eth = data;
+ ip6 = (void*)(eth + 1);
+
+ // I do not believe this can ever happen, but keep the verifier happy...
+ if (data + l2_header_size + sizeof(*ip6) > data_end) {
+ __sync_fetch_and_add(&stat_v->rxErrors, 1);
+ return TC_ACT_SHOT;
+ }
+ };
+
+ // CHECKSUM_COMPLETE is a 16-bit one's complement sum,
+ // thus corrections for it need to be done in 16-byte chunks at even offsets.
+ // IPv6 nexthdr is at offset 6, while hop limit is at offset 7
+ uint8_t old_hl = ip6->hop_limit;
+ --ip6->hop_limit;
+ uint8_t new_hl = ip6->hop_limit;
+
+ // bpf_csum_update() always succeeds if the skb is CHECKSUM_COMPLETE and returns an error
+ // (-ENOTSUPP) if it isn't.
+ bpf_csum_update(skb, 0xFFFF - ntohs(old_hl) + ntohs(new_hl));
+
+ __sync_fetch_and_add(&stat_v->rxPackets, packets);
+ __sync_fetch_and_add(&stat_v->rxBytes, bytes);
+
+ // Overwrite any mac header with the new one
+ *eth = v->macHeader;
+
+ // Redirect to forwarded interface.
+ //
+ // Note that bpf_redirect() cannot fail unless you pass invalid flags.
+ // The redirect actually happens after the ebpf program has already terminated,
+ // and can fail for example for mtu reasons at that point in time, but there's nothing
+ // we can do about it here.
+ return bpf_redirect(v->oif, 0 /* this is effectively BPF_F_EGRESS */);
+}
+
+SEC("schedcls/ingress/tether_ether")
+int sched_cls_ingress_tether_ether(struct __sk_buff* skb) {
+ return do_forward(skb, true);
+}
+
+// Note: section names must be unique to prevent programs from appending to each other,
+// so instead the bpf loader will strip everything past the final $ symbol when actually
+// pinning the program into the filesystem.
+//
+// bpf_skb_change_head() is only present on 4.14+ and 2 trivial kernel patches are needed:
+// ANDROID: net: bpf: Allow TC programs to call BPF_FUNC_skb_change_head
+// ANDROID: net: bpf: permit redirect from ingress L3 to egress L2 devices at near max mtu
+// (the first of those has already been upstreamed)
+//
+// 5.4 kernel support was only added to Android Common Kernel in R,
+// and thus a 5.4 kernel always supports this.
+//
+// Hence, this mandatory (must load successfully) implementation for 5.4+ kernels:
+DEFINE_BPF_PROG_KVER("schedcls/ingress/tether_rawip$5_4", AID_ROOT, AID_ROOT,
+ sched_cls_ingress_tether_rawip_5_4, KVER(5, 4, 0))
+(struct __sk_buff* skb) {
+ return do_forward(skb, false);
+}
+
+// and this identical optional (may fail to load) implementation for [4.14..5.4) patched kernels:
+DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/ingress/tether_rawip$4_14", AID_ROOT, AID_ROOT,
+ sched_cls_ingress_tether_rawip_4_14, KVER(4, 14, 0),
+ KVER(5, 4, 0))
+(struct __sk_buff* skb) {
+ return do_forward(skb, false);
+}
+
+// and define a no-op stub for [4.9,4.14) and unpatched [4.14,5.4) kernels.
+// (if the above real 4.14+ program loaded successfully, then bpfloader will have already pinned
+// it at the same location this one would be pinned at and will thus skip loading this stub)
+DEFINE_BPF_PROG_KVER_RANGE("schedcls/ingress/tether_rawip$stub", AID_ROOT, AID_ROOT,
+ sched_cls_ingress_tether_rawip_stub, KVER_NONE, KVER(5, 4, 0))
+(struct __sk_buff* skb) {
+ return TC_ACT_OK;
+}
+
+LICENSE("Apache 2.0");
+CRITICAL("netd");
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index ee03b150b519..9495fb5fdc77 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;
}
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index 533bbe68e274..92b8608f4f6c 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -261,9 +261,13 @@ final class RemoteAugmentedAutofillService
focusedValue != null && focusedValue.isText()
? focusedValue.getTextValue().toString() : null;
+ final InlineFillUi.InlineFillUiInfo inlineFillUiInfo =
+ new InlineFillUi.InlineFillUiInfo(request, focusedId, filterText,
+ remoteRenderService, userId, sessionId);
+
final InlineFillUi inlineFillUi =
InlineFillUi.forAugmentedAutofill(
- request, inlineSuggestionsData, focusedId, filterText,
+ inlineFillUiInfo, inlineSuggestionsData,
new InlineFillUi.InlineSuggestionUiCallback() {
@Override
public void autofill(Dataset dataset, int datasetIndex) {
@@ -305,15 +309,24 @@ final class RemoteAugmentedAutofillService
}
@Override
- public void startIntentSender(IntentSender intentSender,
- Intent intent) {
+ public void authenticate(int requestId, int datasetIndex) {
+ Slog.e(TAG, "authenticate not implemented for augmented autofill");
+ }
+
+ @Override
+ public void startIntentSender(IntentSender intentSender) {
try {
- client.startIntentSender(intentSender, intent);
+ client.startIntentSender(intentSender, new Intent());
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException starting intent sender");
}
}
- }, onErrorCallback, remoteRenderService, userId, sessionId);
+
+ @Override
+ public void onError() {
+ onErrorCallback.run();
+ }
+ });
if (inlineSuggestionsCallback.apply(inlineFillUi)) {
mCallbacks.logAugmentedAutofillShown(sessionId, clientState);
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 1970b5774bbb..4d2e4f6ee486 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -3008,14 +3008,36 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return false;
}
- InlineFillUi inlineFillUi = InlineFillUi.forAutofill(
- inlineSuggestionsRequest.get(), response, focusedId, filterText,
- /*uiCallback*/this, /*onErrorCallback*/ () -> {
- synchronized (mLock) {
- mInlineSessionController.setInlineFillUiLocked(
- InlineFillUi.emptyUi(focusedId));
+ final InlineFillUi.InlineFillUiInfo inlineFillUiInfo =
+ new InlineFillUi.InlineFillUiInfo(inlineSuggestionsRequest.get(), focusedId,
+ filterText, remoteRenderService, userId, id);
+ InlineFillUi inlineFillUi = InlineFillUi.forAutofill(inlineFillUiInfo, response,
+ new InlineFillUi.InlineSuggestionUiCallback() {
+ @Override
+ public void autofill(@NonNull Dataset dataset, int datasetIndex) {
+ fill(response.getRequestId(), datasetIndex, dataset);
+ }
+
+ @Override
+ public void authenticate(int requestId, int datasetIndex) {
+ Session.this.authenticate(response.getRequestId(), datasetIndex,
+ response.getAuthentication(), response.getClientState(),
+ /* authenticateInline= */ true);
+ }
+
+ @Override
+ public void startIntentSender(@NonNull IntentSender intentSender) {
+ Session.this.startIntentSender(intentSender, new Intent());
}
- }, remoteRenderService, userId, id);
+
+ @Override
+ public void onError() {
+ synchronized (mLock) {
+ mInlineSessionController.setInlineFillUiLocked(
+ InlineFillUi.emptyUi(focusedId));
+ }
+ }
+ });
return mInlineSessionController.setInlineFillUiLocked(inlineFillUi);
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java b/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java
index 25e9d5c90764..ff175904fb61 100644
--- a/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java
@@ -20,7 +20,7 @@ import static com.android.server.autofill.Helper.sVerbose;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.Intent;
+import android.annotation.UserIdInt;
import android.content.IntentSender;
import android.service.autofill.Dataset;
import android.service.autofill.FillResponse;
@@ -32,6 +32,7 @@ import android.util.SparseArray;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
import android.view.inputmethod.InlineSuggestion;
+import android.view.inputmethod.InlineSuggestionInfo;
import android.view.inputmethod.InlineSuggestionsRequest;
import android.view.inputmethod.InlineSuggestionsResponse;
@@ -93,59 +94,72 @@ public final class InlineFillUi {
*/
@NonNull
public static InlineFillUi emptyUi(@NonNull AutofillId autofillId) {
- return new InlineFillUi(autofillId, new SparseArray<>(), null);
+ return new InlineFillUi(autofillId);
+ }
+
+ /**
+ * Encapsulates various arguments used by {@link #forAutofill} and {@link #forAugmentedAutofill}
+ */
+ public static class InlineFillUiInfo {
+
+ public int mUserId;
+ public int mSessionId;
+ public InlineSuggestionsRequest mInlineRequest;
+ public AutofillId mFocusedId;
+ public String mFilterText;
+ public RemoteInlineSuggestionRenderService mRemoteRenderService;
+
+ public InlineFillUiInfo(@NonNull InlineSuggestionsRequest inlineRequest,
+ @NonNull AutofillId focusedId, @NonNull String filterText,
+ @NonNull RemoteInlineSuggestionRenderService remoteRenderService,
+ @UserIdInt int userId, int sessionId) {
+ mUserId = userId;
+ mSessionId = sessionId;
+ mInlineRequest = inlineRequest;
+ mFocusedId = focusedId;
+ mFilterText = filterText;
+ mRemoteRenderService = remoteRenderService;
+ }
}
/**
* Returns an inline autofill UI for a field based on an Autofilll response.
*/
@NonNull
- public static InlineFillUi forAutofill(@NonNull InlineSuggestionsRequest request,
+ public static InlineFillUi forAutofill(@NonNull InlineFillUiInfo inlineFillUiInfo,
@NonNull FillResponse response,
- @NonNull AutofillId focusedViewId, @Nullable String filterText,
- @NonNull AutoFillUI.AutoFillUiCallback uiCallback,
- @NonNull Runnable onErrorCallback,
- @Nullable RemoteInlineSuggestionRenderService remoteRenderService,
- int userId, int sessionId) {
-
- if (InlineSuggestionFactory.responseNeedAuthentication(response)) {
+ @NonNull InlineSuggestionUiCallback uiCallback) {
+ if (response.getAuthentication() != null && response.getInlinePresentation() != null) {
InlineSuggestion inlineAuthentication =
- InlineSuggestionFactory.createInlineAuthentication(request, response,
- uiCallback, onErrorCallback, remoteRenderService, userId, sessionId);
- return new InlineFillUi(focusedViewId, inlineAuthentication, filterText);
+ InlineSuggestionFactory.createInlineAuthentication(inlineFillUiInfo, response,
+ uiCallback);
+ return new InlineFillUi(inlineFillUiInfo, inlineAuthentication);
} else if (response.getDatasets() != null) {
SparseArray<Pair<Dataset, InlineSuggestion>> inlineSuggestions =
- InlineSuggestionFactory.createAutofillInlineSuggestions(request,
- response.getRequestId(),
- response.getDatasets(), focusedViewId, uiCallback, onErrorCallback,
- remoteRenderService, userId, sessionId);
- return new InlineFillUi(focusedViewId, inlineSuggestions, filterText);
+ InlineSuggestionFactory.createInlineSuggestions(inlineFillUiInfo,
+ InlineSuggestionInfo.SOURCE_AUTOFILL, response.getDatasets(),
+ uiCallback);
+ return new InlineFillUi(inlineFillUiInfo, inlineSuggestions);
}
- return new InlineFillUi(focusedViewId, new SparseArray<>(), filterText);
+ return new InlineFillUi(inlineFillUiInfo, new SparseArray<>());
}
/**
* Returns an inline autofill UI for a field based on an Autofilll response.
*/
@NonNull
- public static InlineFillUi forAugmentedAutofill(@NonNull InlineSuggestionsRequest request,
+ public static InlineFillUi forAugmentedAutofill(@NonNull InlineFillUiInfo inlineFillUiInfo,
@NonNull List<Dataset> datasets,
- @NonNull AutofillId focusedViewId, @Nullable String filterText,
- @NonNull InlineSuggestionUiCallback uiCallback,
- @NonNull Runnable onErrorCallback,
- @Nullable RemoteInlineSuggestionRenderService remoteRenderService,
- int userId, int sessionId) {
+ @NonNull InlineSuggestionUiCallback uiCallback) {
SparseArray<Pair<Dataset, InlineSuggestion>> inlineSuggestions =
- InlineSuggestionFactory.createAugmentedAutofillInlineSuggestions(request, datasets,
- focusedViewId,
- uiCallback, onErrorCallback, remoteRenderService, userId, sessionId);
- return new InlineFillUi(focusedViewId, inlineSuggestions, filterText);
+ InlineSuggestionFactory.createInlineSuggestions(inlineFillUiInfo,
+ InlineSuggestionInfo.SOURCE_PLATFORM, datasets, uiCallback);
+ return new InlineFillUi(inlineFillUiInfo, inlineSuggestions);
}
- InlineFillUi(@NonNull AutofillId autofillId,
- @NonNull SparseArray<Pair<Dataset, InlineSuggestion>> inlineSuggestions,
- @Nullable String filterText) {
- mAutofillId = autofillId;
+ private InlineFillUi(@Nullable InlineFillUiInfo inlineFillUiInfo,
+ @NonNull SparseArray<Pair<Dataset, InlineSuggestion>> inlineSuggestions) {
+ mAutofillId = inlineFillUiInfo.mFocusedId;
int size = inlineSuggestions.size();
mDatasets = new ArrayList<>(size);
mInlineSuggestions = new ArrayList<>(size);
@@ -154,16 +168,26 @@ public final class InlineFillUi {
mDatasets.add(value.first);
mInlineSuggestions.add(value.second);
}
- mFilterText = filterText;
+ mFilterText = inlineFillUiInfo.mFilterText;
}
- InlineFillUi(@NonNull AutofillId autofillId, InlineSuggestion inlineSuggestion,
- @Nullable String filterText) {
- mAutofillId = autofillId;
+ private InlineFillUi(@NonNull InlineFillUiInfo inlineFillUiInfo,
+ @NonNull InlineSuggestion inlineSuggestion) {
+ mAutofillId = inlineFillUiInfo.mFocusedId;
mDatasets = null;
mInlineSuggestions = new ArrayList<>();
mInlineSuggestions.add(inlineSuggestion);
- mFilterText = filterText;
+ mFilterText = inlineFillUiInfo.mFilterText;
+ }
+
+ /**
+ * Only used for constructing an empty InlineFillUi with {@link #emptyUi}
+ */
+ private InlineFillUi(@NonNull AutofillId focusedId) {
+ mAutofillId = focusedId;
+ mDatasets = new ArrayList<>(0);
+ mInlineSuggestions = new ArrayList<>(0);
+ mFilterText = null;
}
@NonNull
@@ -295,9 +319,21 @@ public final class InlineFillUi {
void autofill(@NonNull Dataset dataset, int datasetIndex);
/**
+ * Callback to authenticate a dataset.
+ *
+ * <p>Only implemented by regular autofill for now.</p>
+ */
+ void authenticate(int requestId, int datasetIndex);
+
+ /**
* Callback to start Intent in client app.
*/
- void startIntentSender(@NonNull IntentSender intentSender, @NonNull Intent intent);
+ void startIntentSender(@NonNull IntentSender intentSender);
+
+ /**
+ * Callback on errors.
+ */
+ void onError();
}
/**
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
index 8fcb8aa9393c..b15d07b0f1a9 100644
--- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
@@ -20,184 +20,98 @@ import static com.android.server.autofill.Helper.sDebug;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.os.IBinder;
import android.service.autofill.Dataset;
import android.service.autofill.FillResponse;
import android.service.autofill.InlinePresentation;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
-import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
import android.view.inputmethod.InlineSuggestion;
import android.view.inputmethod.InlineSuggestionInfo;
import android.view.inputmethod.InlineSuggestionsRequest;
-import android.view.inputmethod.InlineSuggestionsResponse;
import android.widget.inline.InlinePresentationSpec;
import com.android.internal.view.inline.IInlineContentProvider;
-import com.android.server.autofill.RemoteInlineSuggestionRenderService;
import java.util.List;
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
final class InlineSuggestionFactory {
private static final String TAG = "InlineSuggestionFactory";
- public static boolean responseNeedAuthentication(@NonNull FillResponse response) {
- return response.getAuthentication() != null && response.getInlinePresentation() != null;
- }
-
public static InlineSuggestion createInlineAuthentication(
- @NonNull InlineSuggestionsRequest request, @NonNull FillResponse response,
- @NonNull AutoFillUI.AutoFillUiCallback client, @NonNull Runnable onErrorCallback,
- @Nullable RemoteInlineSuggestionRenderService remoteRenderService, int userId,
- int sessionId) {
- final BiConsumer<Dataset, Integer> onClickFactory = (dataset, datasetIndex) -> {
- client.authenticate(response.getRequestId(),
- datasetIndex, response.getAuthentication(), response.getClientState(),
- /* authenticateInline= */ true);
- };
- final Consumer<IntentSender> intentSenderConsumer = (intentSender) ->
- client.startIntentSender(intentSender, new Intent());
+ @NonNull InlineFillUi.InlineFillUiInfo inlineFillUiInfo, @NonNull FillResponse response,
+ @NonNull InlineFillUi.InlineSuggestionUiCallback uiCallback) {
InlinePresentation inlineAuthentication = response.getInlinePresentation();
- return createInlineAuthSuggestion(
- mergedInlinePresentation(request, 0, inlineAuthentication),
- remoteRenderService, userId, sessionId,
- onClickFactory, onErrorCallback, intentSenderConsumer,
- request.getHostInputToken(), request.getHostDisplayId());
- }
+ final int requestId = response.getRequestId();
- /**
- * Creates an {@link InlineSuggestionsResponse} with the {@code datasets} provided by the
- * autofill service, potentially filtering the datasets.
- */
- @Nullable
- public static SparseArray<Pair<Dataset, InlineSuggestion>> createAutofillInlineSuggestions(
- @NonNull InlineSuggestionsRequest request, int requestId,
- @NonNull List<Dataset> datasets,
- @NonNull AutofillId autofillId,
- @NonNull AutoFillUI.AutoFillUiCallback client, @NonNull Runnable onErrorCallback,
- @Nullable RemoteInlineSuggestionRenderService remoteRenderService,
- int userId, int sessionId) {
- if (sDebug) Slog.d(TAG, "createInlineSuggestionsResponse called");
- final Consumer<IntentSender> intentSenderConsumer = (intentSender) ->
- client.startIntentSender(intentSender, new Intent());
- final BiConsumer<Dataset, Integer> onClickFactory = (dataset, datasetIndex) -> {
- client.fill(requestId, datasetIndex, dataset);
- };
-
- return createInlineSuggestionsInternal(/* isAugmented= */ false, request,
- datasets, autofillId,
- onErrorCallback, onClickFactory, intentSenderConsumer, remoteRenderService, userId,
- sessionId);
+ return createInlineSuggestion(inlineFillUiInfo, InlineSuggestionInfo.SOURCE_AUTOFILL,
+ InlineSuggestionInfo.TYPE_ACTION, () -> uiCallback.authenticate(requestId,
+ AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED),
+ mergedInlinePresentation(inlineFillUiInfo.mInlineRequest, 0, inlineAuthentication),
+ uiCallback);
}
/**
- * Creates an {@link InlineSuggestionsResponse} with the {@code datasets} provided by augmented
- * autofill service.
+ * Creates an array of {@link InlineSuggestion}s with the {@code datasets} provided by either
+ * regular/augmented autofill services.
*/
@Nullable
- public static SparseArray<Pair<Dataset, InlineSuggestion>>
- createAugmentedAutofillInlineSuggestions(
- @NonNull InlineSuggestionsRequest request, @NonNull List<Dataset> datasets,
- @NonNull AutofillId autofillId,
- @NonNull InlineFillUi.InlineSuggestionUiCallback inlineSuggestionUiCallback,
- @NonNull Runnable onErrorCallback,
- @Nullable RemoteInlineSuggestionRenderService remoteRenderService,
- int userId, int sessionId) {
- if (sDebug) Slog.d(TAG, "createAugmentedInlineSuggestionsResponse called");
- return createInlineSuggestionsInternal(/* isAugmented= */ true, request,
- datasets, autofillId, onErrorCallback,
- (dataset, datasetIndex) ->
- inlineSuggestionUiCallback.autofill(dataset, datasetIndex),
- (intentSender) ->
- inlineSuggestionUiCallback.startIntentSender(intentSender, new Intent()),
- remoteRenderService, userId, sessionId);
- }
+ public static SparseArray<Pair<Dataset, InlineSuggestion>> createInlineSuggestions(
+ @NonNull InlineFillUi.InlineFillUiInfo inlineFillUiInfo,
+ @NonNull @InlineSuggestionInfo.Source String suggestionSource,
+ @NonNull List<Dataset> datasets,
+ @NonNull InlineFillUi.InlineSuggestionUiCallback uiCallback) {
+ if (sDebug) Slog.d(TAG, "createInlineSuggestions(source=" + suggestionSource + ") called");
- @Nullable
- private static SparseArray<Pair<Dataset, InlineSuggestion>> createInlineSuggestionsInternal(
- boolean isAugmented, @NonNull InlineSuggestionsRequest request,
- @NonNull List<Dataset> datasets, @NonNull AutofillId autofillId,
- @NonNull Runnable onErrorCallback, @NonNull BiConsumer<Dataset, Integer> onClickFactory,
- @NonNull Consumer<IntentSender> intentSenderConsumer,
- @Nullable RemoteInlineSuggestionRenderService remoteRenderService,
- int userId, int sessionId) {
+ final InlineSuggestionsRequest request = inlineFillUiInfo.mInlineRequest;
SparseArray<Pair<Dataset, InlineSuggestion>> response = new SparseArray<>(datasets.size());
for (int datasetIndex = 0; datasetIndex < datasets.size(); datasetIndex++) {
final Dataset dataset = datasets.get(datasetIndex);
- final int fieldIndex = dataset.getFieldIds().indexOf(autofillId);
+ final int fieldIndex = dataset.getFieldIds().indexOf(inlineFillUiInfo.mFocusedId);
if (fieldIndex < 0) {
- Slog.w(TAG, "AutofillId=" + autofillId + " not found in dataset");
+ Slog.w(TAG, "AutofillId=" + inlineFillUiInfo.mFocusedId + " not found in dataset");
continue;
}
- final InlinePresentation inlinePresentation = dataset.getFieldInlinePresentation(
- fieldIndex);
+
+ final InlinePresentation inlinePresentation =
+ dataset.getFieldInlinePresentation(fieldIndex);
if (inlinePresentation == null) {
Slog.w(TAG, "InlinePresentation not found in dataset");
continue;
}
- InlineSuggestion inlineSuggestion = createInlineSuggestion(isAugmented, dataset,
- datasetIndex,
+
+ final String suggestionType =
+ dataset.getAuthentication() == null ? InlineSuggestionInfo.TYPE_SUGGESTION
+ : InlineSuggestionInfo.TYPE_ACTION;
+ final int index = datasetIndex;
+
+ InlineSuggestion inlineSuggestion = createInlineSuggestion(
+ inlineFillUiInfo, suggestionSource, suggestionType,
+ () -> uiCallback.autofill(dataset, index),
mergedInlinePresentation(request, datasetIndex, inlinePresentation),
- onClickFactory, remoteRenderService, userId, sessionId,
- onErrorCallback, intentSenderConsumer,
- request.getHostInputToken(), request.getHostDisplayId());
+ uiCallback);
response.append(datasetIndex, Pair.create(dataset, inlineSuggestion));
}
+
return response;
}
- private static InlineSuggestion createInlineSuggestion(boolean isAugmented,
- @NonNull Dataset dataset, int datasetIndex,
+ private static InlineSuggestion createInlineSuggestion(
+ @NonNull InlineFillUi.InlineFillUiInfo inlineFillUiInfo,
+ @NonNull @InlineSuggestionInfo.Source String suggestionSource,
+ @NonNull @InlineSuggestionInfo.Type String suggestionType,
+ @NonNull Runnable onClickAction,
@NonNull InlinePresentation inlinePresentation,
- @NonNull BiConsumer<Dataset, Integer> onClickFactory,
- @NonNull RemoteInlineSuggestionRenderService remoteRenderService,
- int userId, int sessionId,
- @NonNull Runnable onErrorCallback, @NonNull Consumer<IntentSender> intentSenderConsumer,
- @Nullable IBinder hostInputToken,
- int displayId) {
- final String suggestionSource = isAugmented ? InlineSuggestionInfo.SOURCE_PLATFORM
- : InlineSuggestionInfo.SOURCE_AUTOFILL;
- final String suggestionType =
- dataset.getAuthentication() == null ? InlineSuggestionInfo.TYPE_SUGGESTION
- : InlineSuggestionInfo.TYPE_ACTION;
+ @NonNull InlineFillUi.InlineSuggestionUiCallback uiCallback) {
final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo(
inlinePresentation.getInlinePresentationSpec(), suggestionSource,
inlinePresentation.getAutofillHints(), suggestionType,
inlinePresentation.isPinned());
- final InlineSuggestion inlineSuggestion = new InlineSuggestion(inlineSuggestionInfo,
- createInlineContentProvider(inlinePresentation,
- () -> onClickFactory.accept(dataset, datasetIndex), onErrorCallback,
- intentSenderConsumer, remoteRenderService, userId, sessionId,
- hostInputToken, displayId));
-
- return inlineSuggestion;
- }
-
- private static InlineSuggestion createInlineAuthSuggestion(
- @NonNull InlinePresentation inlinePresentation,
- @NonNull RemoteInlineSuggestionRenderService remoteRenderService,
- int userId, int sessionId,
- @NonNull BiConsumer<Dataset, Integer> onClickFactory, @NonNull Runnable onErrorCallback,
- @NonNull Consumer<IntentSender> intentSenderConsumer,
- @Nullable IBinder hostInputToken, int displayId) {
- final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo(
- inlinePresentation.getInlinePresentationSpec(),
- InlineSuggestionInfo.SOURCE_AUTOFILL, inlinePresentation.getAutofillHints(),
- InlineSuggestionInfo.TYPE_ACTION, inlinePresentation.isPinned());
-
return new InlineSuggestion(inlineSuggestionInfo,
- createInlineContentProvider(inlinePresentation,
- () -> onClickFactory.accept(null,
- AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED),
- onErrorCallback, intentSenderConsumer, remoteRenderService, userId,
- sessionId, hostInputToken, displayId));
+ createInlineContentProvider(inlineFillUiInfo, inlinePresentation,
+ onClickAction, uiCallback));
}
/**
@@ -216,25 +130,20 @@ final class InlineSuggestionFactory {
inlinePresentation.getInlinePresentationSpec().getMinSize(),
inlinePresentation.getInlinePresentationSpec().getMaxSize()).setStyle(
specFromHost.getStyle()).build();
+
return new InlinePresentation(inlinePresentation.getSlice(), mergedInlinePresentation,
inlinePresentation.isPinned());
}
private static IInlineContentProvider createInlineContentProvider(
+ @NonNull InlineFillUi.InlineFillUiInfo inlineFillUiInfo,
@NonNull InlinePresentation inlinePresentation, @Nullable Runnable onClickAction,
- @NonNull Runnable onErrorCallback,
- @NonNull Consumer<IntentSender> intentSenderConsumer,
- @Nullable RemoteInlineSuggestionRenderService remoteRenderService,
- int userId, int sessionId,
- @Nullable IBinder hostInputToken,
- int displayId) {
- RemoteInlineSuggestionViewConnector
- remoteInlineSuggestionViewConnector = new RemoteInlineSuggestionViewConnector(
- remoteRenderService, userId, sessionId, inlinePresentation, hostInputToken,
- displayId, onClickAction, onErrorCallback, intentSenderConsumer);
- InlineContentProviderImpl inlineContentProvider = new InlineContentProviderImpl(
- remoteInlineSuggestionViewConnector, null);
- return inlineContentProvider;
+ @NonNull InlineFillUi.InlineSuggestionUiCallback uiCallback) {
+ RemoteInlineSuggestionViewConnector remoteInlineSuggestionViewConnector =
+ new RemoteInlineSuggestionViewConnector(inlineFillUiInfo, inlinePresentation,
+ onClickAction, uiCallback);
+
+ return new InlineContentProviderImpl(remoteInlineSuggestionViewConnector, null);
}
private InlineSuggestionFactory() {
diff --git a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
index 7257255d1ee4..46d435d8811d 100644
--- a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
+++ b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
@@ -57,24 +57,20 @@ final class RemoteInlineSuggestionViewConnector {
private final Consumer<IntentSender> mStartIntentSenderFromClientApp;
RemoteInlineSuggestionViewConnector(
- @Nullable RemoteInlineSuggestionRenderService remoteRenderService,
- int userId, int sessionId,
+ @NonNull InlineFillUi.InlineFillUiInfo inlineFillUiInfo,
@NonNull InlinePresentation inlinePresentation,
- @Nullable IBinder hostInputToken,
- int displayId,
@NonNull Runnable onAutofillCallback,
- @NonNull Runnable onErrorCallback,
- @NonNull Consumer<IntentSender> startIntentSenderFromClientApp) {
- mRemoteRenderService = remoteRenderService;
+ @NonNull InlineFillUi.InlineSuggestionUiCallback uiCallback) {
+ mRemoteRenderService = inlineFillUiInfo.mRemoteRenderService;
mInlinePresentation = inlinePresentation;
- mHostInputToken = hostInputToken;
- mDisplayId = displayId;
- mUserId = userId;
- mSessionId = sessionId;
+ mHostInputToken = inlineFillUiInfo.mInlineRequest.getHostInputToken();
+ mDisplayId = inlineFillUiInfo.mInlineRequest.getHostDisplayId();
+ mUserId = inlineFillUiInfo.mUserId;
+ mSessionId = inlineFillUiInfo.mSessionId;
mOnAutofillCallback = onAutofillCallback;
- mOnErrorCallback = onErrorCallback;
- mStartIntentSenderFromClientApp = startIntentSenderFromClientApp;
+ mOnErrorCallback = uiCallback::onError;
+ mStartIntentSenderFromClientApp = uiCallback::startIntentSender;
}
/**
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index c9e5d011f9f5..6ca99d1a244b 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -83,6 +83,7 @@ import com.android.internal.notification.NotificationAccessConfirmationActivityC
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
+import com.android.internal.util.DumpUtils;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.FgThread;
import com.android.server.LocalServices;
@@ -484,6 +485,10 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
public void dump(@NonNull FileDescriptor fd,
@NonNull PrintWriter fout,
@Nullable String[] args) {
+ if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), LOG_TAG, fout)) {
+ return;
+ }
+
fout.append("Companion Device Associations:").append('\n');
synchronized (mLock) {
forEach(mCachedAssociations, a -> {
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 0d79240a4b59..4e405cc7f7ea 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -418,7 +418,8 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
if (BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED.equals(action)) {
String newName = intent.getStringExtra(BluetoothAdapter.EXTRA_LOCAL_NAME);
if (DBG) {
- Slog.d(TAG, "Bluetooth Adapter name changed to " + newName);
+ Slog.d(TAG, "Bluetooth Adapter name changed to " + newName + " by "
+ + mContext.getPackageName());
}
if (newName != null) {
storeNameAndAddress(newName, null);
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 2522e937025b..ba2e147a2acf 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -2145,11 +2145,11 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
if (validatePhoneId(phoneId)) {
mOutgoingSmsEmergencyNumber[phoneId] = emergencyNumber;
for (Record r : mRecords) {
+ // Send to all listeners regardless of subscription
if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_SMS)
- && idMatch(r.subId, subId, phoneId)) {
+ PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_SMS)) {
try {
- r.callback.onOutgoingEmergencySms(emergencyNumber);
+ r.callback.onOutgoingEmergencySms(emergencyNumber, subId);
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 20f3231de200..bfe04e628f81 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2609,12 +2609,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;
+ return ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE_OR_FULL;
}
if (callingPackage.equals(service.getComponent().getPackageName())) {
- return ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE;
+ return ActivityManagerInternal.ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_FULL;
} else {
- return ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
+ return ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE_OR_FULL;
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b1b4018036c0..2de3e525d2da 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5753,11 +5753,6 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
- public void positionTaskInStack(int taskId, int stackId, int position) {
- mActivityTaskManager.positionTaskInStack(taskId, stackId, position);
- }
-
- @Override
public List<RootTaskInfo> getAllRootTaskInfos() {
return mActivityTaskManager.getAllRootTaskInfos();
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 9ce8b11a7351..11197287114c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -2582,8 +2582,6 @@ 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":
@@ -2656,18 +2654,6 @@ final class ActivityManagerShellCommand extends ShellCommand {
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);
- return 0;
- }
-
int runStackList(PrintWriter pw) throws RemoteException {
List<RootTaskInfo> tasks = mTaskInterface.getAllRootTaskInfos();
for (RootTaskInfo info : tasks) {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index f42753287e35..808a6993af1a 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -276,15 +276,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) {
}
}
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 966038986791..8112bb854b71 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -22,6 +22,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import android.app.ActivityManager;
import android.app.ActivityThread;
+import android.app.ApplicationExitInfo;
import android.os.Debug;
import android.os.Handler;
import android.os.Message;
@@ -132,11 +133,15 @@ public final class CachedAppOptimizer {
static final int REPORT_UNFREEZE_MSG = 4;
//TODO:change this static definition into a configurable flag.
- static final int FREEZE_TIMEOUT_MS = 500;
+ static final int FREEZE_TIMEOUT_MS = 10000;
static final int DO_FREEZE = 1;
static final int REPORT_UNFREEZE = 2;
+ // Bitfield values for sync/async transactions reveived by frozen processes
+ static final int SYNC_RECEIVED_WHILE_FROZEN = 1;
+ static final int ASYNC_RECEIVED_WHILE_FROZEN = 2;
+
/**
* This thread must be moved to the system background cpuset.
* If that doesn't happen, it's probably going to draw a lot of power.
@@ -494,6 +499,15 @@ public final class CachedAppOptimizer {
private static native void freezeBinder(int pid, boolean freeze);
/**
+ * Retrieves binder freeze info about a process.
+ * @param pid the pid for which binder freeze info is to be retrieved.
+ *
+ * @throws RuntimeException if the operation could not complete successfully.
+ * @return a bit field reporting the binder freeze info for the process.
+ */
+ private static native int getBinderFreezeInfo(int pid);
+
+ /**
* Determines whether the freezer is supported by this system
*/
public static boolean isFreezerSupported() {
@@ -729,6 +743,37 @@ public final class CachedAppOptimizer {
return;
}
+ boolean processKilled = false;
+
+ try {
+ int freezeInfo = getBinderFreezeInfo(app.pid);
+
+ if ((freezeInfo & SYNC_RECEIVED_WHILE_FROZEN) != 0) {
+ Slog.d(TAG_AM, "pid " + app.pid + " " + app.processName + " "
+ + " received sync transactions while frozen, killing");
+ app.kill("Sync transaction while in frozen state",
+ ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
+ processKilled = true;
+ }
+
+ if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0) {
+ Slog.d(TAG_AM, "pid " + app.pid + " " + app.processName + " "
+ + " received async transactions while frozen");
+ }
+ } catch (Exception e) {
+ Slog.d(TAG_AM, "Unable to query binder frozen info for pid " + app.pid + " "
+ + app.processName + ". Killing it. Exception: " + e);
+ app.kill("Unable to query binder frozen stats",
+ ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
+ processKilled = true;
+ }
+
+ if (processKilled) {
+ return;
+ }
+
long freezeTime = app.freezeUnfreezeTime;
try {
@@ -745,8 +790,12 @@ public final class CachedAppOptimizer {
try {
freezeBinder(app.pid, false);
} catch (RuntimeException e) {
- // TODO: it might be preferable to kill the target pid in this case
- Slog.e(TAG_AM, "Unable to unfreeze binder for " + app.pid + " " + app.processName);
+ Slog.e(TAG_AM, "Unable to unfreeze binder for " + app.pid + " " + app.processName
+ + ". Killing it");
+ app.kill("Unable to unfreeze",
+ ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
+ return;
}
if (DEBUG_FREEZER) {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index eb60573e6f17..3dfbcc71dd3c 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -23,10 +23,11 @@ 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_ALL_PROFILE_PERMISSIONS_IN_PROFILE;
+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_FULL_ONLY;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
-import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
+import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE_OR_FULL;
import static android.os.Process.SHELL_UID;
import static android.os.Process.SYSTEM_UID;
@@ -1911,11 +1912,12 @@ 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) {
+ } else if (allowMode == ALLOW_NON_FULL
+ || allowMode == ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL) {
// We are blanket allowing non-full access, you lucky caller!
allow = true;
- } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE
- || allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) {
+ } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE_OR_FULL
+ || allowMode == ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_FULL) {
// We may or may not allow this depending on whether the two users are
// in the same profile.
allow = isSameProfileGroup;
@@ -1942,12 +1944,15 @@ 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 || isSameProfileGroup) {
+ if (allowMode == ALLOW_NON_FULL
+ || allowMode == ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL
+ || isSameProfileGroup) {
builder.append(" or ");
builder.append(INTERACT_ACROSS_USERS);
}
if (isSameProfileGroup
- && allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) {
+ && (allowMode == ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_FULL
+ || allowMode == ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL)) {
builder.append(" or ");
builder.append(INTERACT_ACROSS_PROFILES);
}
@@ -1974,7 +1979,8 @@ class UserController implements Handler.Callback {
private boolean canInteractWithAcrossProfilesPermission(
int allowMode, boolean isSameProfileGroup, int callingPid, int callingUid,
String callingPackage) {
- if (allowMode != ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) {
+ if (allowMode != ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_FULL
+ && allowMode != ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL) {
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 06f44b1bd388..6099e52e54e9 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -19,6 +19,7 @@ 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;
@@ -36,6 +37,7 @@ 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;
@@ -129,6 +131,7 @@ 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;
@@ -162,6 +165,7 @@ 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;
@@ -1794,9 +1798,9 @@ public class AppOpsService extends IAppOpsService.Stub {
}
}
}
-
- mHistoricalRegistry.clearHistory(uid, packageName);
}
+
+ mHistoricalRegistry.clearHistory(uid, packageName);
}
public void uidRemoved(int uid) {
@@ -2199,8 +2203,11 @@ 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) {
@@ -2245,6 +2252,11 @@ 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);
@@ -2450,8 +2462,12 @@ 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);
- verifyIncomingPackage(packageName, UserHandle.getUserId(uid));
+ verifyIncomingUser(userId);
+ verifyIncomingPackage(packageName, userId);
ArraySet<ModeCallback> repCbs = null;
code = AppOpsManager.opToSwitch(code);
@@ -2713,6 +2729,9 @@ 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) {
@@ -2871,8 +2890,11 @@ public class AppOpsService extends IAppOpsService.Stub {
private int checkOperationImpl(int code, int uid, String packageName,
boolean raw) {
+ int userId = UserHandle.getUserId(uid);
+
verifyIncomingOp(code);
- verifyIncomingPackage(packageName, UserHandle.getUserId(uid));
+ verifyIncomingUser(userId);
+ verifyIncomingPackage(packageName, userId);
String resolvedPackageName = resolvePackageName(uid, packageName);
if (resolvedPackageName == null) {
@@ -2991,10 +3013,15 @@ 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);
- verifyIncomingPackage(proxiedPackageName, UserHandle.getUserId(proxiedUid));
- verifyIncomingPackage(proxyPackageName, UserHandle.getUserId(proxyUid));
+ verifyIncomingUser(proxiedUserId);
+ verifyIncomingUser(proxyUserId);
+ verifyIncomingPackage(proxiedPackageName, proxiedUserId);
+ verifyIncomingPackage(proxyPackageName, proxyUserId);
String resolveProxyPackageName = resolvePackageName(proxyUid, proxyPackageName);
if (resolveProxyPackageName == null) {
@@ -3044,9 +3071,12 @@ 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);
- verifyIncomingPackage(packageName, UserHandle.getUserId(uid));
+ verifyIncomingUser(userId);
+ verifyIncomingPackage(packageName, userId);
String resolvedPackageName = resolvePackageName(uid, packageName);
if (resolvedPackageName == null) {
@@ -3423,9 +3453,12 @@ 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);
- verifyIncomingPackage(packageName, UserHandle.getUserId(uid));
+ verifyIncomingUser(userId);
+ verifyIncomingPackage(packageName, userId);
String resolvedPackageName = resolvePackageName(uid, packageName);
if (resolvedPackageName == null) {
@@ -3517,9 +3550,12 @@ 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);
- verifyIncomingPackage(packageName, UserHandle.getUserId(uid));
+ verifyIncomingUser(userId);
+ verifyIncomingPackage(packageName, userId);
String resolvedPackageName = resolvePackageName(uid, packageName);
if (resolvedPackageName == null) {
@@ -3748,6 +3784,33 @@ 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) {
@@ -5827,8 +5890,11 @@ public class AppOpsService extends IAppOpsService.Stub {
return false;
}
}
+ int userId = UserHandle.getUserId(uid);
+
verifyIncomingOp(code);
- verifyIncomingPackage(packageName, UserHandle.getUserId(uid));
+ verifyIncomingUser(userId);
+ verifyIncomingPackage(packageName, userId);
final String resolvedPackageName = resolvePackageName(uid, packageName);
if (resolvedPackageName == null) {
@@ -6247,7 +6313,7 @@ public class AppOpsService extends IAppOpsService.Stub {
int[] users;
if (userId == UserHandle.USER_ALL) {
- // TODO(b/157921703): this call is returning all users, not just live ones - we
+ // TODO(b/162888972): this call is returning all users, not just live ones - we
// need to either fix the method called, or rename the variable
List<UserInfo> liveUsers = UserManager.get(mContext).getUsers();
diff --git a/services/core/java/com/android/server/appop/TEST_MAPPING b/services/core/java/com/android/server/appop/TEST_MAPPING
index 84de25c06ebf..a3e1b7a7e5c5 100644
--- a/services/core/java/com/android/server/appop/TEST_MAPPING
+++ b/services/core/java/com/android/server/appop/TEST_MAPPING
@@ -7,6 +7,9 @@
"name": "CtsAppOps2TestCases"
},
{
+ "name": "CtsAppOpHostTestCases"
+ },
+ {
"name": "FrameworksServicesTests",
"options": [
{
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 7175489614ea..99dc58e20209 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -256,8 +256,8 @@ public class Vpn {
@GuardedBy("this")
private final Set<UidRange> mBlockedUidsAsToldToNetd = new ArraySet<>();
- // Handle of the user initiating VPN.
- private final int mUserHandle;
+ // The user id of initiating VPN.
+ private final int mUserId;
interface RetryScheduler {
void checkInterruptAndDelay(boolean sleepLonger) throws InterruptedException;
@@ -384,26 +384,26 @@ public class Vpn {
}
public Vpn(Looper looper, Context context, INetworkManagementService netService,
- @UserIdInt int userHandle, @NonNull KeyStore keyStore) {
- this(looper, context, new Dependencies(), netService, userHandle, keyStore,
+ @UserIdInt int userId, @NonNull KeyStore keyStore) {
+ this(looper, context, new Dependencies(), netService, userId, keyStore,
new SystemServices(context), new Ikev2SessionCreator());
}
@VisibleForTesting
protected Vpn(Looper looper, Context context, Dependencies deps,
INetworkManagementService netService,
- int userHandle, @NonNull KeyStore keyStore, SystemServices systemServices,
+ int userId, @NonNull KeyStore keyStore, SystemServices systemServices,
Ikev2SessionCreator ikev2SessionCreator) {
mContext = context;
mDeps = deps;
mNetd = netService;
- mUserHandle = userHandle;
+ mUserId = userId;
mLooper = looper;
mSystemServices = systemServices;
mIkev2SessionCreator = ikev2SessionCreator;
mPackage = VpnConfig.LEGACY_VPN;
- mOwnerUID = getAppUid(mPackage, mUserHandle);
+ mOwnerUID = getAppUid(mPackage, mUserId);
mIsPackageTargetingAtLeastQ = doesPackageTargetAtLeastQ(mPackage);
try {
@@ -613,7 +613,7 @@ public class Vpn {
PackageManager pm = mContext.getPackageManager();
ApplicationInfo appInfo = null;
try {
- appInfo = pm.getApplicationInfoAsUser(packageName, 0 /*flags*/, mUserHandle);
+ appInfo = pm.getApplicationInfoAsUser(packageName, 0 /*flags*/, mUserId);
} catch (NameNotFoundException unused) {
Log.w(TAG, "Can't find \"" + packageName + "\" when checking always-on support");
}
@@ -624,7 +624,7 @@ public class Vpn {
final Intent intent = new Intent(VpnConfig.SERVICE_INTERFACE);
intent.setPackage(packageName);
List<ResolveInfo> services =
- pm.queryIntentServicesAsUser(intent, PackageManager.GET_META_DATA, mUserHandle);
+ pm.queryIntentServicesAsUser(intent, PackageManager.GET_META_DATA, mUserId);
if (services == null || services.size() == 0) {
return false;
}
@@ -769,12 +769,12 @@ public class Vpn {
final long token = Binder.clearCallingIdentity();
try {
mSystemServices.settingsSecurePutStringForUser(Settings.Secure.ALWAYS_ON_VPN_APP,
- getAlwaysOnPackage(), mUserHandle);
+ getAlwaysOnPackage(), mUserId);
mSystemServices.settingsSecurePutIntForUser(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN,
- (mAlwaysOn && mLockdown ? 1 : 0), mUserHandle);
+ (mAlwaysOn && mLockdown ? 1 : 0), mUserId);
mSystemServices.settingsSecurePutStringForUser(
LOCKDOWN_ALLOWLIST_SETTING_NAME,
- String.join(",", mLockdownAllowlist), mUserHandle);
+ String.join(",", mLockdownAllowlist), mUserId);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -786,11 +786,11 @@ public class Vpn {
final long token = Binder.clearCallingIdentity();
try {
final String alwaysOnPackage = mSystemServices.settingsSecureGetStringForUser(
- Settings.Secure.ALWAYS_ON_VPN_APP, mUserHandle);
+ Settings.Secure.ALWAYS_ON_VPN_APP, mUserId);
final boolean alwaysOnLockdown = mSystemServices.settingsSecureGetIntForUser(
- Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN, 0 /*default*/, mUserHandle) != 0;
+ Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN, 0 /*default*/, mUserId) != 0;
final String allowlistString = mSystemServices.settingsSecureGetStringForUser(
- LOCKDOWN_ALLOWLIST_SETTING_NAME, mUserHandle);
+ LOCKDOWN_ALLOWLIST_SETTING_NAME, mUserId);
final List<String> allowedPackages = TextUtils.isEmpty(allowlistString)
? Collections.emptyList() : Arrays.asList(allowlistString.split(","));
setAlwaysOnPackageInternal(
@@ -850,13 +850,13 @@ public class Vpn {
DeviceIdleInternal idleController =
LocalServices.getService(DeviceIdleInternal.class);
idleController.addPowerSaveTempWhitelistApp(Process.myUid(), alwaysOnPackage,
- VPN_LAUNCH_IDLE_ALLOWLIST_DURATION_MS, mUserHandle, false, "vpn");
+ VPN_LAUNCH_IDLE_ALLOWLIST_DURATION_MS, mUserId, false, "vpn");
// Start the VPN service declared in the app's manifest.
Intent serviceIntent = new Intent(VpnConfig.SERVICE_INTERFACE);
serviceIntent.setPackage(alwaysOnPackage);
try {
- return mContext.startServiceAsUser(serviceIntent, UserHandle.of(mUserHandle)) != null;
+ return mContext.startServiceAsUser(serviceIntent, UserHandle.of(mUserId)) != null;
} catch (RuntimeException e) {
Log.e(TAG, "VpnService " + serviceIntent + " failed to start", e);
return false;
@@ -958,7 +958,7 @@ public class Vpn {
// We can't just check that packageName matches mPackage, because if the app was uninstalled
// and reinstalled it will no longer be prepared. Similarly if there is a shared UID, the
// calling package may not be the same as the prepared package. Check both UID and package.
- return getAppUid(packageName, mUserHandle) == mOwnerUID && mPackage.equals(packageName);
+ return getAppUid(packageName, mUserId) == mOwnerUID && mPackage.equals(packageName);
}
/** Prepare the VPN for the given package. Does not perform permission checks. */
@@ -998,7 +998,7 @@ public class Vpn {
Log.i(TAG, "Switched from " + mPackage + " to " + newPackage);
mPackage = newPackage;
- mOwnerUID = getAppUid(newPackage, mUserHandle);
+ mOwnerUID = getAppUid(newPackage, mUserId);
mIsPackageTargetingAtLeastQ = doesPackageTargetAtLeastQ(newPackage);
try {
mNetd.allowProtect(mOwnerUID);
@@ -1019,7 +1019,7 @@ public class Vpn {
// Check if the caller is authorized.
enforceControlPermissionOrInternalCaller();
- final int uid = getAppUid(packageName, mUserHandle);
+ final int uid = getAppUid(packageName, mUserId);
if (uid == -1 || VpnConfig.LEGACY_VPN.equals(packageName)) {
// Authorization for nonexistent packages (or fake ones) can't be updated.
return false;
@@ -1095,14 +1095,14 @@ public class Vpn {
|| isVpnServicePreConsented(context, packageName);
}
- private int getAppUid(final String app, final int userHandle) {
+ private int getAppUid(final String app, final int userId) {
if (VpnConfig.LEGACY_VPN.equals(app)) {
return Process.myUid();
}
PackageManager pm = mContext.getPackageManager();
return Binder.withCleanCallingIdentity(() -> {
try {
- return pm.getPackageUidAsUser(app, userHandle);
+ return pm.getPackageUidAsUser(app, userId);
} catch (NameNotFoundException e) {
return -1;
}
@@ -1116,7 +1116,7 @@ public class Vpn {
PackageManager pm = mContext.getPackageManager();
try {
ApplicationInfo appInfo =
- pm.getApplicationInfoAsUser(packageName, 0 /*flags*/, mUserHandle);
+ pm.getApplicationInfoAsUser(packageName, 0 /*flags*/, mUserId);
return appInfo.targetSdkVersion >= VERSION_CODES.Q;
} catch (NameNotFoundException unused) {
Log.w(TAG, "Can't find \"" + packageName + "\"");
@@ -1241,7 +1241,7 @@ public class Vpn {
mNetworkCapabilities.setOwnerUid(mOwnerUID);
mNetworkCapabilities.setAdministratorUids(new int[] {mOwnerUID});
- mNetworkCapabilities.setUids(createUserAndRestrictedProfilesRanges(mUserHandle,
+ mNetworkCapabilities.setUids(createUserAndRestrictedProfilesRanges(mUserId,
mConfig.allowedApplications, mConfig.disallowedApplications));
long token = Binder.clearCallingIdentity();
try {
@@ -1315,7 +1315,7 @@ public class Vpn {
enforceNotRestrictedUser();
ResolveInfo info = AppGlobals.getPackageManager().resolveService(intent,
- null, 0, mUserHandle);
+ null, 0, mUserId);
if (info == null) {
throw new SecurityException("Cannot find " + config.user);
}
@@ -1352,7 +1352,7 @@ public class Vpn {
Connection connection = new Connection();
if (!mContext.bindServiceAsUser(intent, connection,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
- new UserHandle(mUserHandle))) {
+ new UserHandle(mUserId))) {
throw new IllegalStateException("Cannot bind " + config.user);
}
@@ -1427,10 +1427,10 @@ public class Vpn {
}
// Note: Return type guarantees results are deduped and sorted, which callers require.
- private SortedSet<Integer> getAppsUids(List<String> packageNames, int userHandle) {
+ private SortedSet<Integer> getAppsUids(List<String> packageNames, int userId) {
SortedSet<Integer> uids = new TreeSet<>();
for (String app : packageNames) {
- int uid = getAppUid(app, userHandle);
+ int uid = getAppUid(app, userId);
if (uid != -1) uids.add(uid);
}
return uids;
@@ -1444,22 +1444,22 @@ public class Vpn {
* the UID ranges will match the app list specified there. Otherwise, all UIDs
* in each user and profile will be included.
*
- * @param userHandle The userId to create UID ranges for along with any of its restricted
+ * @param userId The userId to create UID ranges for along with any of its restricted
* profiles.
* @param allowedApplications (optional) List of applications to allow.
* @param disallowedApplications (optional) List of applications to deny.
*/
@VisibleForTesting
- Set<UidRange> createUserAndRestrictedProfilesRanges(@UserIdInt int userHandle,
+ Set<UidRange> createUserAndRestrictedProfilesRanges(@UserIdInt int userId,
@Nullable List<String> allowedApplications,
@Nullable List<String> disallowedApplications) {
final Set<UidRange> ranges = new ArraySet<>();
// Assign the top-level user to the set of ranges
- addUserToRanges(ranges, userHandle, allowedApplications, disallowedApplications);
+ addUserToRanges(ranges, userId, allowedApplications, disallowedApplications);
// If the user can have restricted profiles, assign all its restricted profiles too
- if (canHaveRestrictedProfile(userHandle)) {
+ if (canHaveRestrictedProfile(userId)) {
final long token = Binder.clearCallingIdentity();
List<UserInfo> users;
try {
@@ -1468,7 +1468,7 @@ public class Vpn {
Binder.restoreCallingIdentity(token);
}
for (UserInfo user : users) {
- if (user.isRestricted() && (user.restrictedProfileParentId == userHandle)) {
+ if (user.isRestricted() && (user.restrictedProfileParentId == userId)) {
addUserToRanges(ranges, user.id, allowedApplications, disallowedApplications);
}
}
@@ -1485,18 +1485,18 @@ public class Vpn {
* in the user will be included.
*
* @param ranges {@link Set} of {@link UidRange}s to which to add.
- * @param userHandle The userId to add to {@param ranges}.
+ * @param userId The userId to add to {@param ranges}.
* @param allowedApplications (optional) allowlist of applications to include.
* @param disallowedApplications (optional) denylist of applications to exclude.
*/
@VisibleForTesting
- void addUserToRanges(@NonNull Set<UidRange> ranges, @UserIdInt int userHandle,
+ void addUserToRanges(@NonNull Set<UidRange> ranges, @UserIdInt int userId,
@Nullable List<String> allowedApplications,
@Nullable List<String> disallowedApplications) {
if (allowedApplications != null) {
// Add ranges covering all UIDs for allowedApplications.
int start = -1, stop = -1;
- for (int uid : getAppsUids(allowedApplications, userHandle)) {
+ for (int uid : getAppsUids(allowedApplications, userId)) {
if (start == -1) {
start = uid;
} else if (uid != stop + 1) {
@@ -1508,9 +1508,9 @@ public class Vpn {
if (start != -1) ranges.add(new UidRange(start, stop));
} else if (disallowedApplications != null) {
// Add all ranges for user skipping UIDs for disallowedApplications.
- final UidRange userRange = UidRange.createForUser(userHandle);
+ final UidRange userRange = UidRange.createForUser(userId);
int start = userRange.start;
- for (int uid : getAppsUids(disallowedApplications, userHandle)) {
+ for (int uid : getAppsUids(disallowedApplications, userId)) {
if (uid == start) {
start++;
} else {
@@ -1521,16 +1521,16 @@ public class Vpn {
if (start <= userRange.stop) ranges.add(new UidRange(start, userRange.stop));
} else {
// Add all UIDs for the user.
- ranges.add(UidRange.createForUser(userHandle));
+ ranges.add(UidRange.createForUser(userId));
}
}
// Returns the subset of the full list of active UID ranges the VPN applies to (mVpnUsers) that
- // apply to userHandle.
- static private List<UidRange> uidRangesForUser(int userHandle, Set<UidRange> existingRanges) {
+ // apply to userId.
+ private static List<UidRange> uidRangesForUser(int userId, Set<UidRange> existingRanges) {
// UidRange#createForUser returns the entire range of UIDs available to a macro-user.
// This is something like 0-99999 ; {@see UserHandle#PER_USER_RANGE}
- final UidRange userRange = UidRange.createForUser(userHandle);
+ final UidRange userRange = UidRange.createForUser(userId);
final List<UidRange> ranges = new ArrayList<>();
for (UidRange range : existingRanges) {
if (userRange.containsRange(range)) {
@@ -1545,15 +1545,15 @@ public class Vpn {
*
* <p>Should be called on primary ConnectivityService thread.
*/
- public void onUserAdded(int userHandle) {
+ public void onUserAdded(int userId) {
// If the user is restricted tie them to the parent user's VPN
- UserInfo user = UserManager.get(mContext).getUserInfo(userHandle);
- if (user.isRestricted() && user.restrictedProfileParentId == mUserHandle) {
+ UserInfo user = UserManager.get(mContext).getUserInfo(userId);
+ if (user.isRestricted() && user.restrictedProfileParentId == mUserId) {
synchronized(Vpn.this) {
final Set<UidRange> existingRanges = mNetworkCapabilities.getUids();
if (existingRanges != null) {
try {
- addUserToRanges(existingRanges, userHandle, mConfig.allowedApplications,
+ addUserToRanges(existingRanges, userId, mConfig.allowedApplications,
mConfig.disallowedApplications);
// ConnectivityService will call {@link #updateCapabilities} and apply
// those for VPN network.
@@ -1572,16 +1572,16 @@ public class Vpn {
*
* <p>Should be called on primary ConnectivityService thread.
*/
- public void onUserRemoved(int userHandle) {
+ public void onUserRemoved(int userId) {
// clean up if restricted
- UserInfo user = UserManager.get(mContext).getUserInfo(userHandle);
- if (user.isRestricted() && user.restrictedProfileParentId == mUserHandle) {
+ UserInfo user = UserManager.get(mContext).getUserInfo(userId);
+ if (user.isRestricted() && user.restrictedProfileParentId == mUserId) {
synchronized(Vpn.this) {
final Set<UidRange> existingRanges = mNetworkCapabilities.getUids();
if (existingRanges != null) {
try {
final List<UidRange> removedRanges =
- uidRangesForUser(userHandle, existingRanges);
+ uidRangesForUser(userId, existingRanges);
existingRanges.removeAll(removedRanges);
// ConnectivityService will call {@link #updateCapabilities} and
// apply those for VPN network.
@@ -1639,7 +1639,7 @@ public class Vpn {
final Set<UidRange> rangesToTellNetdToAdd;
if (enforce) {
final Set<UidRange> rangesThatShouldBeBlocked =
- createUserAndRestrictedProfilesRanges(mUserHandle,
+ createUserAndRestrictedProfilesRanges(mUserId,
/* allowedApplications */ null,
/* disallowedApplications */ exemptedPackages);
@@ -1909,7 +1909,7 @@ public class Vpn {
private void updateAlwaysOnNotification(DetailedState networkState) {
final boolean visible = (mAlwaysOn && networkState != DetailedState.CONNECTED);
- final UserHandle user = UserHandle.of(mUserHandle);
+ final UserHandle user = UserHandle.of(mUserId);
final long token = Binder.clearCallingIdentity();
try {
final NotificationManager notificationManager = NotificationManager.from(mContext);
@@ -2019,7 +2019,7 @@ public class Vpn {
private void enforceNotRestrictedUser() {
Binder.withCleanCallingIdentity(() -> {
final UserManager mgr = UserManager.get(mContext);
- final UserInfo user = mgr.getUserInfo(mUserHandle);
+ final UserInfo user = mgr.getUserInfo(mUserId);
if (user.isRestricted()) {
throw new SecurityException("Restricted users cannot configure VPNs");
@@ -2054,9 +2054,9 @@ public class Vpn {
public void startLegacyVpnPrivileged(VpnProfile profile, KeyStore keyStore,
LinkProperties egress) {
UserManager mgr = UserManager.get(mContext);
- UserInfo user = mgr.getUserInfo(mUserHandle);
+ UserInfo user = mgr.getUserInfo(mUserId);
if (user.isRestricted() || mgr.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN,
- new UserHandle(mUserHandle))) {
+ new UserHandle(mUserId))) {
throw new SecurityException("Restricted users cannot establish VPNs");
}
@@ -2984,14 +2984,14 @@ public class Vpn {
}
private void verifyCallingUidAndPackage(String packageName) {
- if (getAppUid(packageName, mUserHandle) != Binder.getCallingUid()) {
+ if (getAppUid(packageName, mUserId) != Binder.getCallingUid()) {
throw new SecurityException("Mismatched package and UID");
}
}
@VisibleForTesting
String getProfileNameForPackage(String packageName) {
- return Credentials.PLATFORM_VPN + mUserHandle + "_" + packageName;
+ return Credentials.PLATFORM_VPN + mUserId + "_" + packageName;
}
@VisibleForTesting
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/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 4a12ee71adbe..97c4cf531a53 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1179,6 +1179,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) {
@@ -2463,6 +2472,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);
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/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 0be428bdba47..507a265f0203 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) {
@@ -942,7 +942,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) {
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..bb6f14c0f6c4 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -28,9 +28,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 +125,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 +140,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 +626,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 +634,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 +666,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
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 44b6a63faea1..b407234457a4 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) {
@@ -3233,6 +3237,10 @@ 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.
for (HdmiCecLocalDevice device : getAllLocalDevices()) {
@@ -3363,6 +3371,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/location/AbstractLocationProvider.java b/services/core/java/com/android/server/location/AbstractLocationProvider.java
index 06e7a762d28e..56982a81f83b 100644
--- a/services/core/java/com/android/server/location/AbstractLocationProvider.java
+++ b/services/core/java/com/android/server/location/AbstractLocationProvider.java
@@ -16,8 +16,6 @@
package com.android.server.location;
-import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable;
-
import android.annotation.Nullable;
import android.location.Location;
import android.location.util.identity.CallerIdentity;
@@ -342,8 +340,7 @@ public abstract class AbstractLocationProvider {
*/
public final void setRequest(ProviderRequest request) {
// all calls into the provider must be moved onto the provider thread to prevent deadlock
- mExecutor.execute(obtainRunnable(AbstractLocationProvider::onSetRequest, this, request)
- .recycleOnUse());
+ mExecutor.execute(() -> onSetRequest(request));
}
/**
@@ -356,13 +353,7 @@ public abstract class AbstractLocationProvider {
*/
public final void sendExtraCommand(int uid, int pid, String command, Bundle extras) {
// all calls into the provider must be moved onto the provider thread to prevent deadlock
-
- // the integer boxing done here likely cancels out any gains from removing lambda
- // allocation, but since this an infrequently used api with no real performance needs, we
- // we use pooled lambdas anyways for consistency.
- mExecutor.execute(
- obtainRunnable(AbstractLocationProvider::onExtraCommand, this, uid, pid, command,
- extras).recycleOnUse());
+ mExecutor.execute(() -> onExtraCommand(uid, pid, command, extras));
}
/**
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 807784dde505..72734c47873e 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -701,10 +701,11 @@ public class LocationManagerService extends ILocationManager.Stub {
return location;
}
+ @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(),
@@ -721,8 +722,7 @@ public class LocationManagerService extends ILocationManager.Stub {
Preconditions.checkArgument(manager != null,
"provider \"" + provider + "\" does not exist");
- manager.getCurrentLocation(request, identity, permissionLevel, cancellationTransport,
- consumer);
+ return manager.getCurrentLocation(request, identity, permissionLevel, consumer);
}
@Override
diff --git a/services/core/java/com/android/server/location/LocationProviderManager.java b/services/core/java/com/android/server/location/LocationProviderManager.java
index e224a9d85115..138301ae934d 100644
--- a/services/core/java/com/android/server/location/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/LocationProviderManager.java
@@ -29,6 +29,7 @@ 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_THROTTLE_REQUESTS_WHEN_SCREEN_OFF;
+import static com.android.internal.location.ProviderRequest.EMPTY_REQUEST;
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;
@@ -77,6 +78,7 @@ import android.util.SparseBooleanArray;
import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
import com.android.internal.location.ProviderProperties;
import com.android.internal.location.ProviderRequest;
import com.android.internal.util.Preconditions;
@@ -111,7 +113,8 @@ import java.util.List;
import java.util.Objects;
class LocationProviderManager extends
- ListenerMultiplexer<Object, LocationRequest, LocationProviderManager.LocationTransport,
+ ListenerMultiplexer<Object, LocationProviderManager.LocationTransport,
+ LocationProviderManager.LocationListenerOperation,
LocationProviderManager.Registration, ProviderRequest> implements
AbstractLocationProvider.Listener {
@@ -121,16 +124,16 @@ class LocationProviderManager extends
private static final String WAKELOCK_TAG = "*location*";
private static final long WAKELOCK_TIMEOUT_MS = 30 * 1000;
- // maximum interval to be considered "high power" request
+ // max interval to be considered "high power" request
private static final long MAX_HIGH_POWER_INTERVAL_MS = 5 * 60 * 1000;
- // maximum age of a location before it is no longer considered "current"
+ // max age of a location before it is no longer considered "current"
private static final long MAX_CURRENT_LOCATION_AGE_MS = 10 * 1000;
// max timeout allowed for getting the current location
private static final long GET_CURRENT_LOCATION_MAX_TIMEOUT_MS = 30 * 1000;
- // maximum jitter allowed for fastest interval evaluation
+ // max jitter allowed for fastest interval evaluation
private static final int MAX_FASTEST_INTERVAL_JITTER_MS = 100;
protected interface LocationTransport {
@@ -214,8 +217,15 @@ class LocationProviderManager extends
}
}
- protected abstract class Registration extends
- RemoteListenerRegistration<LocationRequest, LocationTransport> {
+ protected interface LocationListenerOperation extends ListenerOperation<LocationTransport> {
+ /**
+ * Must be implemented to return the location this operation intends to deliver.
+ */
+ Location getLocation();
+ }
+
+ protected abstract class Registration extends RemoteListenerRegistration<LocationRequest,
+ LocationTransport, LocationListenerOperation> {
@PermissionLevel protected final int mPermissionLevel;
private final WorkSource mWorkSource;
@@ -226,9 +236,11 @@ class LocationProviderManager extends
private LocationRequest mProviderLocationRequest;
private boolean mIsUsingHighPower;
+ @Nullable private Location mLastLocation = null;
+
protected Registration(LocationRequest request, CallerIdentity identity,
LocationTransport transport, @PermissionLevel int permissionLevel) {
- super(TAG, Objects.requireNonNull(request), identity, transport);
+ super(Objects.requireNonNull(request), identity, transport);
Preconditions.checkArgument(permissionLevel > PERMISSION_NONE);
mPermissionLevel = permissionLevel;
@@ -291,7 +303,11 @@ class LocationProviderManager extends
protected void onProviderListenerUnregister() {}
@Override
- protected final ListenerOperation<LocationTransport> onActive() {
+ protected final LocationListenerOperation onActive() {
+ if (Build.IS_DEBUGGABLE) {
+ Preconditions.checkState(Thread.holdsLock(mLock));
+ }
+
if (!getRequest().isHiddenFromAppOps()) {
mLocationAttributionHelper.reportLocationStart(getIdentity(), getName(), getKey());
}
@@ -300,7 +316,11 @@ class LocationProviderManager extends
}
@Override
- protected final ListenerOperation<LocationTransport> onInactive() {
+ protected final LocationListenerOperation onInactive() {
+ if (Build.IS_DEBUGGABLE) {
+ Preconditions.checkState(Thread.holdsLock(mLock));
+ }
+
onHighPowerUsageChanged();
if (!getRequest().isHiddenFromAppOps()) {
mLocationAttributionHelper.reportLocationStop(getIdentity(), getName(), getKey());
@@ -309,18 +329,14 @@ class LocationProviderManager extends
}
@Override
- public final <Listener> void onOperationFailure(ListenerOperation<Listener> operation,
- Exception e) {
- synchronized (mLock) {
- super.onOperationFailure(operation, e);
- }
- }
-
- @Override
public final LocationRequest getRequest() {
return mProviderLocationRequest;
}
+ public final Location getLastDeliveredLocation() {
+ return mLastLocation;
+ }
+
public final boolean isForeground() {
return mForeground;
}
@@ -480,8 +496,17 @@ class LocationProviderManager extends
return mLocationManagerInternal.isProvider(null, getIdentity());
}
+ @GuardedBy("mLock")
+ @Override
+ protected final LocationListenerOperation onExecuteOperation(
+ LocationListenerOperation operation) {
+ mLastLocation = operation.getLocation();
+ return super.onExecuteOperation(operation);
+ }
+
+ @GuardedBy("mLock")
@Nullable
- abstract ListenerOperation<LocationTransport> acceptLocationChange(Location fineLocation);
+ abstract LocationListenerOperation acceptLocationChange(Location fineLocation);
@Override
public String toString() {
@@ -514,7 +539,6 @@ class LocationProviderManager extends
private final PowerManager.WakeLock mWakeLock;
private volatile ProviderTransport mProviderTransport;
- @Nullable private Location mLastLocation = null;
private int mNumLocationsDelivered = 0;
private long mExpirationRealtimeMs = Long.MAX_VALUE;
@@ -606,7 +630,7 @@ class LocationProviderManager extends
@GuardedBy("mLock")
@Nullable
@Override
- ListenerOperation<LocationTransport> acceptLocationChange(Location fineLocation) {
+ LocationListenerOperation acceptLocationChange(Location fineLocation) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
@@ -631,11 +655,12 @@ class LocationProviderManager extends
throw new AssertionError();
}
- if (mLastLocation != null) {
+ Location lastDeliveredLocation = getLastDeliveredLocation();
+ if (lastDeliveredLocation != null) {
// check fastest interval
long deltaMs = NANOSECONDS.toMillis(
location.getElapsedRealtimeNanos()
- - mLastLocation.getElapsedRealtimeNanos());
+ - lastDeliveredLocation.getElapsedRealtimeNanos());
if (deltaMs < getRequest().getMinUpdateIntervalMillis()
- MAX_FASTEST_INTERVAL_JITTER_MS) {
return null;
@@ -643,7 +668,7 @@ class LocationProviderManager extends
// check smallest displacement
double smallestDisplacementM = getRequest().getMinUpdateDistanceMeters();
- if (smallestDisplacementM > 0.0 && location.distanceTo(mLastLocation)
+ if (smallestDisplacementM > 0.0 && location.distanceTo(lastDeliveredLocation)
<= smallestDisplacementM) {
return null;
}
@@ -658,10 +683,12 @@ class LocationProviderManager extends
return null;
}
- // update last location
- mLastLocation = location;
+ return new LocationListenerOperation() {
+ @Override
+ public Location getLocation() {
+ return location;
+ }
- return new ListenerOperation<LocationTransport>() {
@Override
public void onPreExecute() {
// don't acquire a wakelock for mock locations to prevent abuse
@@ -720,8 +747,12 @@ class LocationProviderManager extends
// we choose not to hold a wakelock for provider enabled changed events
executeSafely(getExecutor(), () -> mProviderTransport,
- listener -> listener.deliverOnProviderEnabledChanged(mName, enabled));
+ listener -> listener.deliverOnProviderEnabledChanged(mName, enabled),
+ this::onProviderOperationFailure);
}
+
+ protected abstract void onProviderOperationFailure(
+ ListenerOperation<ProviderTransport> operation, Exception exception);
}
protected final class LocationListenerRegistration extends LocationRegistration implements
@@ -749,6 +780,28 @@ class LocationProviderManager extends
}
@Override
+ protected void onProviderOperationFailure(ListenerOperation<ProviderTransport> operation,
+ Exception exception) {
+ onTransportFailure(exception);
+ }
+
+ @Override
+ public void onOperationFailure(LocationListenerOperation operation, Exception exception) {
+ onTransportFailure(exception);
+ }
+
+ private void onTransportFailure(Exception exception) {
+ if (exception instanceof RemoteException) {
+ Log.w(TAG, "registration " + this + " removed", exception);
+ synchronized (mLock) {
+ remove();
+ }
+ } else {
+ throw new AssertionError(exception);
+ }
+ }
+
+ @Override
public void binderDied() {
try {
if (D) {
@@ -788,6 +841,28 @@ class LocationProviderManager extends
}
@Override
+ protected void onProviderOperationFailure(ListenerOperation<ProviderTransport> operation,
+ Exception exception) {
+ onTransportFailure(exception);
+ }
+
+ @Override
+ public void onOperationFailure(LocationListenerOperation operation, Exception exception) {
+ onTransportFailure(exception);
+ }
+
+ private void onTransportFailure(Exception exception) {
+ if (exception instanceof PendingIntent.CanceledException) {
+ Log.w(TAG, "registration " + this + " removed", exception);
+ synchronized (mLock) {
+ remove();
+ }
+ } else {
+ throw new AssertionError(exception);
+ }
+ }
+
+ @Override
public void onCancelled(PendingIntent intent) {
synchronized (mLock) {
remove();
@@ -838,13 +913,17 @@ class LocationProviderManager extends
0, this, FgThread.getHandler(), getWorkSource());
}
- // start listening for provider enabled/disabled events
- addEnabledListener(this);
+ // 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 the provider is currently disabled fail immediately
+ int userId = getIdentity().getUserId();
+ if (!getRequest().isLocationSettingsIgnored() && !isEnabled(userId)) {
+ deliverLocation(null);
+ }
}
}
@@ -880,7 +959,7 @@ class LocationProviderManager extends
@GuardedBy("mLock")
@Nullable
@Override
- ListenerOperation<LocationTransport> acceptLocationChange(@Nullable Location fineLocation) {
+ LocationListenerOperation acceptLocationChange(@Nullable Location fineLocation) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
@@ -909,19 +988,35 @@ class LocationProviderManager extends
}
}
- return listener -> {
- // if delivering to the same process, make a copy of the location first (since
- // location is mutable)
- Location deliveryLocation = location;
- if (getIdentity().getPid() == Process.myPid() && location != null) {
- deliveryLocation = new Location(location);
+ return new LocationListenerOperation() {
+ @Override
+ public Location getLocation() {
+ return location;
}
- // we currently don't hold a wakelock for getCurrentLocation deliveries
- listener.deliverOnLocationChanged(deliveryLocation, null);
+ @Override
+ public void operate(LocationTransport listener) {
+ // if delivering to the same process, make a copy of the location first (since
+ // location is mutable)
+ Location deliveryLocation = location;
+ if (getIdentity().getPid() == Process.myPid() && location != null) {
+ deliveryLocation = new Location(location);
+ }
- synchronized (mLock) {
- remove();
+ // we currently don't hold a wakelock for getCurrentLocation deliveries
+ try {
+ listener.deliverOnLocationChanged(deliveryLocation, null);
+ } catch (Exception exception) {
+ if (exception instanceof RemoteException) {
+ Log.w(TAG, "registration " + this + " failed", exception);
+ } else {
+ throw new AssertionError(exception);
+ }
+ }
+
+ synchronized (mLock) {
+ remove();
+ }
}
};
}
@@ -1054,6 +1149,11 @@ class LocationProviderManager extends
mProvider = new MockableLocationProvider(mLock, this);
}
+ @Override
+ public String getTag() {
+ return TAG;
+ }
+
public void startManager() {
synchronized (mLock) {
mStarted = true;
@@ -1260,8 +1360,7 @@ class LocationProviderManager extends
/**
* This function does not perform any permissions or safety checks, by calling it you are
- * committing to performing all applicable checks yourself. Prefer
- * {@link #getLastLocation(CallerIdentity, int, boolean)} where possible.
+ * committing to performing all applicable checks yourself.
*/
@Nullable
public Location getLastLocationUnsafe(int userId, @PermissionLevel int permissionLevel,
@@ -1325,9 +1424,9 @@ class LocationProviderManager extends
}
}
- public void getCurrentLocation(LocationRequest request, CallerIdentity callerIdentity,
- int permissionLevel, ICancellationSignal cancellationTransport,
- ILocationCallback callback) {
+ @Nullable
+ public ICancellationSignal getCurrentLocation(LocationRequest request,
+ CallerIdentity callerIdentity, 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)
@@ -1345,15 +1444,15 @@ class LocationProviderManager extends
if (mSettingsHelper.isLocationPackageBlacklisted(callerIdentity.getUserId(),
callerIdentity.getPackageName())) {
registration.deliverLocation(null);
- return;
+ return null;
}
if (!mUserInfoHelper.isCurrentUserId(callerIdentity.getUserId())) {
registration.deliverLocation(null);
- return;
+ return null;
}
if (!request.isLocationSettingsIgnored() && !isEnabled(callerIdentity.getUserId())) {
registration.deliverLocation(null);
- return;
+ return null;
}
Location lastLocation = getLastLocationUnsafe(callerIdentity.getUserId(),
@@ -1364,13 +1463,13 @@ class LocationProviderManager extends
- lastLocation.getElapsedRealtimeNanos());
if (locationAgeMs < MAX_CURRENT_LOCATION_AGE_MS) {
registration.deliverLocation(lastLocation);
- return;
+ return null;
}
if (!mAppForegroundHelper.isAppForeground(Binder.getCallingUid())
&& locationAgeMs < mSettingsHelper.getBackgroundThrottleIntervalMs()) {
registration.deliverLocation(null);
- return;
+ return null;
}
}
@@ -1383,16 +1482,15 @@ class LocationProviderManager extends
}
}
- CancellationSignal cancellationSignal = CancellationSignal.fromTransport(
- cancellationTransport);
- if (cancellationSignal != null) {
- cancellationSignal.setOnCancelListener(SingleUseCallback.wrap(
- () -> {
- synchronized (mLock) {
- removeRegistration(callback.asBinder(), registration);
- }
- }));
- }
+ ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+ CancellationSignal cancellationSignal = CancellationSignal.fromTransport(cancelTransport);
+ cancellationSignal.setOnCancelListener(SingleUseCallback.wrap(
+ () -> {
+ synchronized (mLock) {
+ removeRegistration(callback.asBinder(), registration);
+ }
+ }));
+ return cancelTransport;
}
public void sendExtraCommand(int uid, int pid, String command, Bundle extras) {
@@ -1554,7 +1652,8 @@ class LocationProviderManager extends
@GuardedBy("mLock")
@Override
- protected boolean registerWithService(ProviderRequest mergedRequest) {
+ protected boolean registerWithService(ProviderRequest mergedRequest,
+ Collection<Registration> registrations) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
@@ -1566,8 +1665,8 @@ class LocationProviderManager extends
@GuardedBy("mLock")
@Override
protected boolean reregisterWithService(ProviderRequest oldRequest,
- ProviderRequest newRequest) {
- return registerWithService(newRequest);
+ ProviderRequest newRequest, Collection<Registration> registrations) {
+ return registerWithService(newRequest, registrations);
}
@GuardedBy("mLock")
@@ -1576,7 +1675,8 @@ class LocationProviderManager extends
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
- mProvider.setRequest(ProviderRequest.EMPTY_REQUEST);
+
+ mProvider.setRequest(EMPTY_REQUEST);
}
@GuardedBy("mLock")
@@ -1628,7 +1728,7 @@ class LocationProviderManager extends
@GuardedBy("mLock")
@Override
- protected ProviderRequest mergeRequests(Collection<Registration> registrations) {
+ protected ProviderRequest mergeRegistrations(Collection<Registration> registrations) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
@@ -1814,9 +1914,7 @@ class LocationProviderManager extends
}
// attempt listener delivery
- deliverToListeners(registration -> {
- return registration.acceptLocationChange(location);
- });
+ deliverToListeners(registration -> registration.acceptLocationChange(location));
}
@GuardedBy("mLock")
@@ -1951,7 +2049,8 @@ class LocationProviderManager extends
public void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
synchronized (mLock) {
- ipw.print(mName + " provider");
+ ipw.print(mName);
+ ipw.print(" provider");
if (mProvider.isMock()) {
ipw.print(" [mock]");
}
@@ -1963,12 +2062,15 @@ class LocationProviderManager extends
int[] userIds = mUserInfoHelper.getRunningUserIds();
for (int userId : userIds) {
if (userIds.length != 1) {
- ipw.println("user " + userId + ":");
+ ipw.print("user ");
+ ipw.print(userId);
+ ipw.println(":");
ipw.increaseIndent();
}
- ipw.println(
- "last location=" + getLastLocationUnsafe(userId, PERMISSION_FINE, false));
- ipw.println("enabled=" + isEnabled(userId));
+ ipw.print("last location=");
+ ipw.println(getLastLocationUnsafe(userId, PERMISSION_FINE, false));
+ ipw.print("enabled=");
+ ipw.println(isEnabled(userId));
if (userIds.length != 1) {
ipw.decreaseIndent();
}
diff --git a/services/core/java/com/android/server/location/PassiveLocationProviderManager.java b/services/core/java/com/android/server/location/PassiveLocationProviderManager.java
index 5133683b0625..afeb6444c40b 100644
--- a/services/core/java/com/android/server/location/PassiveLocationProviderManager.java
+++ b/services/core/java/com/android/server/location/PassiveLocationProviderManager.java
@@ -64,7 +64,7 @@ class PassiveLocationProviderManager extends LocationProviderManager {
}
@Override
- protected ProviderRequest mergeRequests(Collection<Registration> registrations) {
+ protected ProviderRequest mergeRegistrations(Collection<Registration> registrations) {
ProviderRequest.Builder providerRequest = new ProviderRequest.Builder()
.setIntervalMillis(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 7f02b2a807c3..58e6d593ed2d 100644
--- a/services/core/java/com/android/server/location/geofence/GeofenceManager.java
+++ b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
@@ -41,6 +41,7 @@ import android.stats.location.LocationStatsEnums;
import android.util.ArraySet;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
import com.android.server.PendingIntentUtils;
import com.android.server.location.LocationPermissions;
import com.android.server.location.listeners.ListenerMultiplexer;
@@ -59,7 +60,7 @@ import java.util.Objects;
* Manages all geofences.
*/
public class GeofenceManager extends
- ListenerMultiplexer<GeofenceKey, Geofence, PendingIntent,
+ ListenerMultiplexer<GeofenceKey, PendingIntent, ListenerOperation<PendingIntent>,
GeofenceManager.GeofenceRegistration, LocationRequest> implements
LocationListener {
@@ -94,7 +95,7 @@ public class GeofenceManager extends
protected GeofenceRegistration(Geofence geofence, CallerIdentity identity,
PendingIntent pendingIntent) {
- super(TAG, geofence, identity, pendingIntent);
+ super(geofence, identity, pendingIntent);
mCenter = new Location("");
mCenter.setLatitude(geofence.getLatitude());
@@ -273,6 +274,11 @@ public class GeofenceManager extends
mLocationUsageLogger = injector.getLocationUsageLogger();
}
+ @Override
+ public String getTag() {
+ return TAG;
+ }
+
private LocationManager getLocationManager() {
synchronized (mLock) {
if (mLocationManager == null) {
@@ -372,7 +378,8 @@ public class GeofenceManager extends
}
@Override
- protected boolean registerWithService(LocationRequest locationRequest) {
+ protected boolean registerWithService(LocationRequest locationRequest,
+ Collection<GeofenceRegistration> registrations) {
getLocationManager().requestLocationUpdates(FUSED_PROVIDER, locationRequest,
DIRECT_EXECUTOR, this);
return true;
@@ -387,7 +394,7 @@ public class GeofenceManager extends
}
@Override
- protected LocationRequest mergeRequests(Collection<GeofenceRegistration> registrations) {
+ protected LocationRequest mergeRegistrations(Collection<GeofenceRegistration> registrations) {
Location location = getLastLocation();
long realtimeMs = SystemClock.elapsedRealtime();
diff --git a/services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java b/services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java
index 5ddc6a1e401d..b99e26988abf 100644
--- a/services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java
@@ -28,6 +28,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import com.android.server.location.util.Injector;
+import java.util.Collection;
import java.util.List;
/**
@@ -59,7 +60,8 @@ public class GnssAntennaInfoProvider extends
}
@Override
- protected boolean registerWithService(Void ignored) {
+ protected boolean registerWithService(Void ignored,
+ Collection<GnssListenerRegistration> registrations) {
Preconditions.checkState(mNative.isAntennaInfoSupported());
if (mNative.startAntennaInfoListening()) {
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 a9fdacca9a06..a830e2fe7973 100644
--- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
@@ -26,11 +26,13 @@ import android.location.LocationManagerInternal;
import android.location.LocationManagerInternal.ProviderEnabledListener;
import android.location.util.identity.CallerIdentity;
import android.os.Binder;
+import android.os.Build;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Process;
import android.util.ArraySet;
+import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.location.listeners.BinderListenerRegistration;
@@ -43,6 +45,7 @@ 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;
/**
@@ -52,14 +55,15 @@ import java.util.Objects;
* Listeners must be registered with the associated IBinder as the key, if the IBinder dies, the
* registration will automatically be removed.
*
- * @param <TRequest> request type
- * @param <TListener> listener type
- * @param <TMergedRequest> merged request type
+ * @param <TRequest> request type
+ * @param <TListener> listener type
+ * @param <TMergedRegistration> merged registration type
*/
public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInterface,
- TMergedRequest> extends
- ListenerMultiplexer<IBinder, TRequest, TListener, GnssListenerMultiplexer<TRequest,
- TListener, TMergedRequest>.GnssListenerRegistration, TMergedRequest> {
+ TMergedRegistration> extends
+ ListenerMultiplexer<IBinder, TListener, ListenerOperation<TListener>,
+ GnssListenerMultiplexer<TRequest, TListener, TMergedRegistration>
+ .GnssListenerRegistration, TMergedRegistration> {
/**
* Registration object for GNSS listeners.
@@ -74,11 +78,11 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
protected GnssListenerRegistration(@Nullable TRequest request,
CallerIdentity callerIdentity, TListener listener) {
- super(TAG, request, callerIdentity, listener);
+ super(request, callerIdentity, listener);
}
@Override
- protected GnssListenerMultiplexer<TRequest, TListener, TMergedRequest> getOwner() {
+ protected GnssListenerMultiplexer<TRequest, TListener, TMergedRegistration> getOwner() {
return GnssListenerMultiplexer.this;
}
@@ -199,6 +203,11 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
LocalServices.getService(LocationManagerInternal.class));
}
+ @Override
+ public String getTag() {
+ return TAG;
+ }
+
/**
* May be overridden by subclasses to return whether the service is supported or not. This value
* should never change for the lifetime of the multiplexer. If the service is unsupported, all
@@ -271,6 +280,21 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
return mLocationManagerInternal.isProvider(null, identity);
}
+ // this provides a default implementation for all further subclasses which assumes that there is
+ // never an associated request object, and thus nothing interesting to merge. the majority of
+ // gnss listener multiplexers do not current have associated requests, and the ones that do can
+ // override this implementation.
+ protected TMergedRegistration mergeRegistrations(
+ Collection<GnssListenerRegistration> gnssListenerRegistrations) {
+ if (Build.IS_DEBUGGABLE) {
+ for (GnssListenerRegistration registration : gnssListenerRegistrations) {
+ Preconditions.checkState(registration.getRequest() == null);
+ }
+ }
+
+ return null;
+ }
+
@Override
protected void onRegister() {
if (!isServiceSupported()) {
diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
index 37db02337a2f..2faa15fac42f 100644
--- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
@@ -76,7 +76,8 @@ public class GnssMeasurementsProvider extends
}
@Override
- protected boolean registerWithService(Boolean fullTrackingRequest) {
+ protected boolean registerWithService(Boolean fullTrackingRequest,
+ Collection<GnssListenerRegistration> registrations) {
Preconditions.checkState(mNative.isMeasurementSupported());
if (mNative.startMeasurementCollection(fullTrackingRequest)) {
@@ -121,7 +122,7 @@ public class GnssMeasurementsProvider extends
}
@Override
- protected Boolean mergeRequests(Collection<GnssListenerRegistration> registrations) {
+ protected Boolean mergeRegistrations(Collection<GnssListenerRegistration> registrations) {
if (mSettingsHelper.isGnssMeasurementsFullTrackingEnabled()) {
return true;
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java b/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java
index 7dcffc664f52..861abc3ef592 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java
@@ -30,6 +30,8 @@ import com.android.internal.util.Preconditions;
import com.android.server.location.util.AppOpsHelper;
import com.android.server.location.util.Injector;
+import java.util.Collection;
+
/**
* An base implementation for GPS navigation messages provider.
* It abstracts out the responsibility of handling listeners, while still allowing technology
@@ -61,7 +63,8 @@ public class GnssNavigationMessageProvider extends
}
@Override
- protected boolean registerWithService(Void ignored) {
+ protected boolean registerWithService(Void ignored,
+ Collection<GnssListenerRegistration> registrations) {
Preconditions.checkState(mNative.isNavigationMessageSupported());
if (mNative.startNavigationMessageCollection()) {
diff --git a/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java b/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java
index 68813b3e0777..248d38a940dd 100644
--- a/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java
@@ -31,6 +31,8 @@ import com.android.server.location.util.AppOpsHelper;
import com.android.server.location.util.Injector;
import com.android.server.location.util.LocationUsageLogger;
+import java.util.Collection;
+
/**
* Implementation of a handler for {@link IGnssStatusListener}.
*/
@@ -51,7 +53,8 @@ public class GnssStatusProvider extends GnssListenerMultiplexer<Void, IGnssStatu
}
@Override
- protected boolean registerWithService(Void ignored) {
+ protected boolean registerWithService(Void ignored,
+ Collection<GnssListenerRegistration> registrations) {
if (D) {
Log.d(TAG, "starting gnss status");
}
diff --git a/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java b/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java
index 58aabdad056f..bc675ceda970 100644
--- a/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java
@@ -23,6 +23,8 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
+import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
+
/**
* A registration that works with IBinder keys, and registers a DeathListener to automatically
* remove the registration if the binder dies. The key for this registration must either be an
@@ -32,7 +34,8 @@ import android.util.Log;
* @param <TListener> listener type
*/
public abstract class BinderListenerRegistration<TRequest, TListener> extends
- RemoteListenerRegistration<TRequest, TListener> implements Binder.DeathRecipient {
+ RemoteListenerRegistration<TRequest, TListener, ListenerOperation<TListener>> implements
+ Binder.DeathRecipient {
/**
* Interface to allow binder retrieval when keys are not themselves IBinders.
@@ -44,9 +47,9 @@ public abstract class BinderListenerRegistration<TRequest, TListener> extends
IBinder getBinder();
}
- protected BinderListenerRegistration(String tag, @Nullable TRequest request,
- CallerIdentity callerIdentity, TListener listener) {
- super(tag, request, callerIdentity, listener);
+ protected BinderListenerRegistration(@Nullable TRequest request, CallerIdentity callerIdentity,
+ TListener listener) {
+ super(request, callerIdentity, listener);
}
@Override
@@ -78,10 +81,20 @@ public abstract class BinderListenerRegistration<TRequest, TListener> extends
protected void onBinderListenerUnregister() {}
@Override
+ public void onOperationFailure(ListenerOperation<TListener> operation, Exception e) {
+ if (e instanceof RemoteException) {
+ Log.w(getOwner().getTag(), "registration " + this + " removed", e);
+ remove();
+ } else {
+ super.onOperationFailure(operation, e);
+ }
+ }
+
+ @Override
public void binderDied() {
try {
- if (Log.isLoggable(mTag, Log.DEBUG)) {
- Log.d(mTag, "binder registration " + getIdentity() + " died");
+ if (Log.isLoggable(getOwner().getTag(), Log.DEBUG)) {
+ Log.d(getOwner().getTag(), "binder registration " + getIdentity() + " died");
}
remove();
} catch (RuntimeException e) {
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 feb4fbd1de31..87d668a07d70 100644
--- a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
@@ -37,12 +37,20 @@ import java.util.function.Function;
import java.util.function.Predicate;
/**
- * A base class to multiplex client listener registrations within system server. Registrations are
- * divided into two categories, active registrations and inactive registrations, as defined by
- * {@link #isActive(ListenerRegistration)}. If a registration's active state changes,
+ * A base class to multiplex client listener registrations within system server. Every listener is
+ * represented by a registration object which stores all required state for a listener. Keys are
+ * used to uniquely identify every registration. Listener operations may be executed on
+ * registrations in order to invoke the represented listener.
+ *
+ * Registrations are divided into two categories, active registrations and inactive registrations,
+ * as defined by {@link #isActive(ListenerRegistration)}. If a registration's active state changes,
* {@link #updateRegistrations(Predicate)} must be invoked and return true for any registration
* whose active state may have changed. Listeners will only be invoked for active registrations.
*
+ * The set of active registrations is combined into a single merged registration, which is submitted
+ * to the backing service when necessary in order to register the service. The merged registration
+ * is updated whenever the set of active registration changes.
+ *
* Callbacks invoked for various changes will always be ordered according to this lifecycle list:
*
* <ul>
@@ -63,14 +71,16 @@ import java.util.function.Predicate;
* {@link #removeRegistration(Object, ListenerRegistration)}, not via any other removal method. This
* ensures re-entrant removal does not accidentally remove the incorrect registration.
*
- * @param <TKey> key type
- * @param <TRequest> request type
- * @param <TListener> listener type
- * @param <TRegistration> registration type
- * @param <TMergedRequest> merged request type
+ * @param <TKey> key type
+ * @param <TListener> listener type
+ * @param <TListenerOperation> listener operation type
+ * @param <TRegistration> registration type
+ * @param <TMergedRegistration> merged registration type
*/
-public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
- TRegistration extends ListenerRegistration<TRequest, TListener>, TMergedRequest> {
+public abstract class ListenerMultiplexer<TKey, TListener,
+ TListenerOperation extends ListenerOperation<TListener>,
+ TRegistration extends ListenerRegistration<TListener, TListenerOperation>,
+ TMergedRegistration> {
@GuardedBy("mRegistrations")
private final ArrayMap<TKey, TRegistration> mRegistrations = new ArrayMap<>();
@@ -88,25 +98,42 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
private boolean mServiceRegistered = false;
@GuardedBy("mRegistrations")
- @Nullable private TMergedRequest mCurrentRequest;
+ @Nullable private TMergedRegistration mMerged;
+
+ /**
+ * Should be implemented to return a unique identifying tag that may be used for logging, etc...
+ */
+ public abstract @NonNull String getTag();
/**
- * Should be implemented to register with the backing service with the given merged request, and
- * should return true if a matching call to {@link #unregisterWithService()} is required to
- * unregister (ie, if registration succeeds).
+ * Should be implemented to register with the backing service with the given merged
+ * registration, and should return true if a matching call to {@link #unregisterWithService()}
+ * is required to unregister (ie, if registration succeeds). The set of registrations passed in
+ * is the same set passed into {@link #mergeRegistrations(Collection)} to generate the merged
+ * registration.
*
- * @see #reregisterWithService(Object, Object)
+ * <p class="note">It may seem redundant to pass in the set of active registrations when they
+ * have already been used to generate the merged request, and indeed, for many implementations
+ * this parameter can likely simply be ignored. However, some implementations may require access
+ * to the set of registrations used to generate the merged requestion for further logic even
+ * after the merged registration has been generated.
+ *
+ * @see #mergeRegistrations(Collection)
+ * @see #reregisterWithService(Object, Object, Collection)
*/
- protected abstract boolean registerWithService(TMergedRequest newRequest);
+ protected abstract boolean registerWithService(TMergedRegistration merged,
+ @NonNull Collection<TRegistration> registrations);
/**
- * Invoked when the service already has a request, and it is being replaced with a new request.
- * The default implementation unregisters first, then registers with the new merged request, but
- * this may be overridden by subclasses in order to reregister more efficiently.
+ * Invoked when the service has already been registered with some merged registration, and is
+ * now being registered with a different merged registration. The default implementation simply
+ * invokes {@link #registerWithService(Object, Collection)}.
+ *
+ * @see #registerWithService(Object, Collection)
*/
- protected boolean reregisterWithService(TMergedRequest oldRequest, TMergedRequest newRequest) {
- unregisterWithService();
- return registerWithService(newRequest);
+ protected boolean reregisterWithService(TMergedRegistration oldMerged,
+ TMergedRegistration newMerged, @NonNull Collection<TRegistration> registrations) {
+ return registerWithService(newMerged, registrations);
}
/**
@@ -116,28 +143,23 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
/**
* Defines whether a registration is currently active or not. Only active registrations will be
- * considered within {@link #mergeRequests(Collection)} to calculate the merged request, and
- * listener invocations will only be delivered to active requests. If a registration's active
- * state changes, {@link #updateRegistrations(Predicate)} must be invoked with a function that
- * returns true for any registrations that may have changed their active state.
+ * forwarded to {@link #registerWithService(Object, Collection)}, and listener invocations will
+ * only be delivered to active requests. If a registration's active state changes,
+ * {@link #updateRegistrations(Predicate)} must be invoked with a function that returns true for
+ * any registrations that may have changed their active state.
*/
protected abstract boolean isActive(@NonNull TRegistration registration);
/**
- * Called in order to generate a merged request from the given registrations. The list of
- * registrations will never be empty.
+ * Called in order to generate a merged registration from the given set of active registrations.
+ * The list of registrations will never be empty. If the resulting merged registration is equal
+ * to the currently registered merged registration, nothing further will happen. If the merged
+ * registration differs, {@link #registerWithService(Object, Collection)} or
+ * {@link #reregisterWithService(Object, Object, Collection)} will be invoked with the new
+ * merged registration so that the backing service can be updated.
*/
- protected @Nullable TMergedRequest mergeRequests(
- @NonNull Collection<TRegistration> registrations) {
- if (Build.IS_DEBUGGABLE) {
- for (TRegistration registration : registrations) {
- // if using non-null requests then implementations must override this method
- Preconditions.checkState(registration.getRequest() == null);
- }
- }
-
- return null;
- }
+ protected abstract @Nullable TMergedRegistration mergeRegistrations(
+ @NonNull Collection<TRegistration> registrations);
/**
* Invoked when the multiplexer goes from having no registrations to having some registrations.
@@ -348,41 +370,42 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
}
if (actives.isEmpty()) {
- mCurrentRequest = null;
if (mServiceRegistered) {
+ mMerged = null;
mServiceRegistered = false;
- mCurrentRequest = null;
unregisterWithService();
}
return;
}
- TMergedRequest merged = mergeRequests(actives);
- if (!mServiceRegistered || !Objects.equals(merged, mCurrentRequest)) {
- if (mServiceRegistered) {
- mServiceRegistered = reregisterWithService(mCurrentRequest, merged);
- } else {
- mServiceRegistered = registerWithService(merged);
- }
+ TMergedRegistration merged = mergeRegistrations(actives);
+ if (!mServiceRegistered || !Objects.equals(merged, mMerged)) {
if (mServiceRegistered) {
- mCurrentRequest = merged;
+ mServiceRegistered = reregisterWithService(mMerged, merged, actives);
} else {
- mCurrentRequest = null;
+ mServiceRegistered = registerWithService(merged, actives);
}
+ mMerged = mServiceRegistered ? merged : null;
}
}
}
/**
- * Clears currently stored service state, and invokes {@link #updateService()} to force a new
- * call to {@link #registerWithService(Object)} if necessary. This is useful, for instance, if
- * the backing service has crashed or otherwise lost state, and needs to be re-initialized.
+ * If the service is currently registered, unregisters it and then calls
+ * {@link #updateService()} so that {@link #registerWithService(Object, Collection)} will be
+ * re-invoked. This is useful, for instance, if the backing service has crashed or otherwise
+ * lost state, and needs to be re-initialized. Because this unregisters first, this is safe to
+ * use even if there is a possibility the backing server has not crashed, or has already been
+ * reinitialized.
*/
protected final void resetService() {
synchronized (mRegistrations) {
- mServiceRegistered = false;
- mCurrentRequest = null;
- updateService();
+ if (mServiceRegistered) {
+ mMerged = null;
+ mServiceRegistered = false;
+ unregisterWithService();
+ updateService();
+ }
}
}
@@ -435,12 +458,12 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
if (++mActiveRegistrationsCount == 1) {
onActive();
}
- ListenerOperation<TListener> operation = registration.onActive();
+ TListenerOperation operation = registration.onActive();
if (operation != null) {
execute(registration, operation);
}
} else {
- ListenerOperation<TListener> operation = registration.onInactive();
+ TListenerOperation operation = registration.onInactive();
if (operation != null) {
execute(registration, operation);
}
@@ -459,14 +482,14 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
* change the active state of the registration.
*/
protected final void deliverToListeners(
- @NonNull Function<TRegistration, ListenerOperation<TListener>> function) {
+ @NonNull Function<TRegistration, TListenerOperation> function) {
synchronized (mRegistrations) {
try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
final int size = mRegistrations.size();
for (int i = 0; i < size; i++) {
TRegistration registration = mRegistrations.valueAt(i);
if (registration.isActive()) {
- ListenerOperation<TListener> operation = function.apply(registration);
+ TListenerOperation operation = function.apply(registration);
if (operation != null) {
execute(registration, operation);
}
@@ -483,7 +506,7 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
* deliverToListeners(registration -> operation);
* </pre>
*/
- protected final void deliverToListeners(@NonNull ListenerOperation<TListener> operation) {
+ protected final void deliverToListeners(@NonNull TListenerOperation operation) {
synchronized (mRegistrations) {
try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
final int size = mRegistrations.size();
@@ -502,7 +525,7 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
onRegistrationActiveChanged(registration);
}
- private void execute(TRegistration registration, ListenerOperation<TListener> operation) {
+ private void execute(TRegistration registration, TListenerOperation operation) {
registration.executeInternal(operation);
}
@@ -539,10 +562,11 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
*/
protected void dumpServiceState(PrintWriter pw) {
if (mServiceRegistered) {
- if (mCurrentRequest == null) {
- pw.print("registered");
- } else {
- pw.print("registered with " + mCurrentRequest);
+ pw.print("registered");
+ if (mMerged != null) {
+ pw.print(" [");
+ pw.print(mMerged);
+ pw.print("]");
}
} else {
pw.print("unregistered");
diff --git a/services/core/java/com/android/server/location/listeners/ListenerRegistration.java b/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
index deb9660a1c82..d7ecbcb7cfdf 100644
--- a/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
@@ -21,6 +21,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import com.android.internal.listeners.ListenerExecutor;
+import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -29,22 +30,21 @@ import java.util.concurrent.Executor;
* A listener registration object which holds data associated with the listener, such as an optional
* request, and an executor responsible for listener invocations.
*
- * @param <TRequest> request type
- * @param <TListener> listener type
+ * @param <TListener> listener type
+ * @param <TListenerOperation> listener operation type
*/
-public class ListenerRegistration<TRequest, TListener> implements ListenerExecutor {
+public class ListenerRegistration<TListener,
+ TListenerOperation extends ListenerOperation<TListener>> implements
+ ListenerExecutor {
private final Executor mExecutor;
- private final @Nullable TRequest mRequest;
private boolean mActive;
private volatile @Nullable TListener mListener;
- protected ListenerRegistration(Executor executor, @Nullable TRequest request,
- TListener listener) {
+ protected ListenerRegistration(Executor executor, TListener listener) {
mExecutor = Objects.requireNonNull(executor);
- mRequest = request;
mActive = false;
mListener = Objects.requireNonNull(listener);
}
@@ -54,13 +54,6 @@ public class ListenerRegistration<TRequest, TListener> implements ListenerExecut
}
/**
- * Returns the request associated with this listener, or null if one wasn't supplied.
- */
- public @Nullable TRequest getRequest() {
- return mRequest;
- }
-
- /**
* May be overridden by subclasses. Invoked when registration occurs. Invoked while holding the
* owning multiplexer's internal lock.
*/
@@ -77,7 +70,7 @@ public class ListenerRegistration<TRequest, TListener> implements ListenerExecut
* returns a non-null operation, that operation will be invoked for the listener. Invoked
* while holding the owning multiplexer's internal lock.
*/
- protected @Nullable ListenerOperation<TListener> onActive() {
+ protected @Nullable TListenerOperation onActive() {
return null;
}
@@ -86,7 +79,7 @@ public class ListenerRegistration<TRequest, TListener> implements ListenerExecut
* a non-null operation, that operation will be invoked for the listener. Invoked while holding
* the owning multiplexer's internal lock.
*/
- protected @Nullable ListenerOperation<TListener> onInactive() {
+ protected @Nullable TListenerOperation onInactive() {
return null;
}
@@ -115,21 +108,38 @@ public class ListenerRegistration<TRequest, TListener> implements ListenerExecut
/**
* May be overridden by subclasses, however should rarely be needed. Invoked when the listener
* associated with this registration is unregistered, which may occur before the registration
- * itself is unregistered. This immediately prevents the listener from being further invoked.
+ * itself is unregistered. This immediately prevents the listener from being further invoked
+ * until the registration itself can be finalized and unregistered completely.
*/
- protected void onListenerUnregister() {};
+ protected void onListenerUnregister() {}
- final void executeInternal(@NonNull ListenerOperation<TListener> operation) {
- executeSafely(mExecutor, () -> mListener, operation);
+ /**
+ * May be overridden by subclasses, however should rarely be needed. Invoked whenever a listener
+ * operation is submitted for execution, and allows the registration a chance to replace the
+ * listener operation or perform related bookkeeping. There is no guarantee a listener operation
+ * submitted or returned here will ever be invoked. Will always be invoked on the calling
+ * thread.
+ */
+ protected TListenerOperation onExecuteOperation(@NonNull TListenerOperation operation) {
+ return operation;
+ }
+
+ /**
+ * May be overridden by subclasses to handle listener operation failures. The default behavior
+ * is to further propagate any exceptions. Will always be invoked on the executor thread.
+ */
+ protected void onOperationFailure(TListenerOperation operation, Exception exception) {
+ throw new AssertionError(exception);
+ }
+
+ final void executeInternal(@NonNull TListenerOperation operation) {
+ executeSafely(mExecutor, () -> mListener,
+ onExecuteOperation(Objects.requireNonNull(operation)), this::onOperationFailure);
}
@Override
public String toString() {
- if (mRequest == null) {
- return "[]";
- } else {
- return mRequest.toString();
- }
+ return "[]";
}
@Override
diff --git a/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java b/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java
index 7b6154eb0d00..e57b5322de8d 100644
--- a/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java
@@ -21,6 +21,8 @@ import android.app.PendingIntent;
import android.location.util.identity.CallerIdentity;
import android.util.Log;
+import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
+
/**
* A registration that works with PendingIntent keys, and registers a CancelListener to
* automatically remove the registration if the PendingIntent is canceled. The key for this
@@ -30,7 +32,8 @@ import android.util.Log;
* @param <TListener> listener type
*/
public abstract class PendingIntentListenerRegistration<TRequest, TListener> extends
- RemoteListenerRegistration<TRequest, TListener> implements PendingIntent.CancelListener {
+ RemoteListenerRegistration<TRequest, TListener, ListenerOperation<TListener>> implements
+ PendingIntent.CancelListener {
/**
* Interface to allowed pending intent retrieval when keys are not themselves PendingIntents.
@@ -42,9 +45,9 @@ public abstract class PendingIntentListenerRegistration<TRequest, TListener> ext
PendingIntent getPendingIntent();
}
- protected PendingIntentListenerRegistration(String tag, @Nullable TRequest request,
+ protected PendingIntentListenerRegistration(@Nullable TRequest request,
CallerIdentity callerIdentity, TListener listener) {
- super(tag, request, callerIdentity, listener);
+ super(request, callerIdentity, listener);
}
@Override
@@ -70,9 +73,20 @@ public abstract class PendingIntentListenerRegistration<TRequest, TListener> ext
protected void onPendingIntentListenerUnregister() {}
@Override
+ public void onOperationFailure(ListenerOperation<TListener> operation, Exception e) {
+ if (e instanceof PendingIntent.CanceledException) {
+ Log.w(getOwner().getTag(), "registration " + this + " removed", e);
+ remove();
+ } else {
+ super.onOperationFailure(operation, e);
+ }
+ }
+
+ @Override
public void onCancelled(PendingIntent intent) {
- if (Log.isLoggable(mTag, Log.DEBUG)) {
- Log.d(mTag, "pending intent registration " + getIdentity() + " canceled");
+ if (Log.isLoggable(getOwner().getTag(), Log.DEBUG)) {
+ Log.d(getOwner().getTag(),
+ "pending intent registration " + getIdentity() + " canceled");
}
remove();
diff --git a/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java
index e4b0b190d34c..242bf323f6cd 100644
--- a/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java
@@ -24,6 +24,7 @@ import android.location.util.identity.CallerIdentity;
import android.os.Process;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
import com.android.server.FgThread;
import java.util.Objects;
@@ -35,11 +36,13 @@ import java.util.concurrent.Executor;
* invocation should already be asynchronous. Listeners from the same process will be run on a
* normal executor, since in-process listener invocation may be synchronous.
*
- * @param <TRequest> request type
- * @param <TListener> listener type
+ * @param <TRequest> request type
+ * @param <TListener> listener type
+ * @param <TListenerOperation> listener operation type
*/
-public abstract class RemoteListenerRegistration<TRequest, TListener> extends
- RemovableListenerRegistration<TRequest, TListener> {
+public abstract class RemoteListenerRegistration<TRequest, TListener,
+ TListenerOperation extends ListenerOperation<TListener>> extends
+ RemovableListenerRegistration<TRequest, TListener, TListenerOperation> {
@VisibleForTesting
public static final Executor IN_PROCESS_EXECUTOR = FgThread.getExecutor();
@@ -59,9 +62,9 @@ public abstract class RemoteListenerRegistration<TRequest, TListener> extends
private final CallerIdentity mIdentity;
- protected RemoteListenerRegistration(String tag, @Nullable TRequest request,
- CallerIdentity identity, TListener listener) {
- super(tag, chooseExecutor(identity), request, listener);
+ protected RemoteListenerRegistration(@Nullable TRequest request, CallerIdentity identity,
+ TListener listener) {
+ super(chooseExecutor(identity), request, listener);
mIdentity = Objects.requireNonNull(identity);
}
diff --git a/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
index 2383bece4e0a..d3b5f6696167 100644
--- a/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
@@ -17,7 +17,8 @@
package com.android.server.location.listeners;
import android.annotation.Nullable;
-import android.util.Log;
+
+import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -26,20 +27,19 @@ import java.util.concurrent.Executor;
* A listener registration that stores its own key, and thus can remove itself. By default it will
* remove itself if any checked exception occurs on listener execution.
*
- * @param <TRequest> request type
- * @param <TListener> listener type
+ * @param <TRequest> request type
+ * @param <TListener> listener type
+ * @param <TListenerOperation> listener operation type
*/
-public abstract class RemovableListenerRegistration<TRequest, TListener> extends
- ListenerRegistration<TRequest, TListener> {
-
- protected final String mTag;
+public abstract class RemovableListenerRegistration<TRequest, TListener,
+ TListenerOperation extends ListenerOperation<TListener>> extends
+ RequestListenerRegistration<TRequest, TListener, TListenerOperation> {
private volatile @Nullable Object mKey;
- protected RemovableListenerRegistration(String tag, Executor executor,
- @Nullable TRequest request, TListener listener) {
+ protected RemovableListenerRegistration(Executor executor, @Nullable TRequest request,
+ TListener listener) {
super(executor, request, listener);
- mTag = Objects.requireNonNull(tag);
}
/**
@@ -47,7 +47,8 @@ public abstract class RemovableListenerRegistration<TRequest, TListener> extends
* with. Often this is easiest to accomplish by defining registration subclasses as non-static
* inner classes of the multiplexer they are to be used with.
*/
- protected abstract ListenerMultiplexer<?, ? super TRequest, ? super TListener, ?, ?> getOwner();
+ protected abstract ListenerMultiplexer<?, ? super TListener, ?
+ super TListenerOperation, ?, ?> getOwner();
/**
* Returns the key associated with this registration. May not be invoked before
@@ -69,12 +70,6 @@ public abstract class RemovableListenerRegistration<TRequest, TListener> extends
}
@Override
- public <Listener> void onOperationFailure(ListenerOperation<Listener> operation, Exception e) {
- Log.w(mTag, "registration " + this + " removed due to unexpected exception", e);
- remove();
- }
-
- @Override
protected final void onRegister(Object key) {
mKey = Objects.requireNonNull(key);
onRemovableListenerRegister();
diff --git a/services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java
new file mode 100644
index 000000000000..d97abae59dd3
--- /dev/null
+++ b/services/core/java/com/android/server/location/listeners/RequestListenerRegistration.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.server.location.listeners;
+
+import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
+
+import java.util.concurrent.Executor;
+
+/**
+ * A listener registration object which includes an associated request.
+ *
+ * @param <TRequest> request type
+ * @param <TListener> listener type
+ * @param <TListenerOperation> listener operation type
+ */
+public class RequestListenerRegistration<TRequest, TListener,
+ TListenerOperation extends ListenerOperation<TListener>> extends
+ ListenerRegistration<TListener, TListenerOperation> {
+
+ private final TRequest mRequest;
+
+ protected RequestListenerRegistration(Executor executor, TRequest request,
+ TListener listener) {
+ super(executor, listener);
+ mRequest = request;
+ }
+
+ /**
+ * Returns the request associated with this listener, or null if one wasn't supplied.
+ */
+ public TRequest getRequest() {
+ return mRequest;
+ }
+
+ @Override
+ public String toString() {
+ if (mRequest == null) {
+ return "[]";
+ } else {
+ return mRequest.toString();
+ }
+ }
+}
+
diff --git a/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java b/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java
new file mode 100644
index 000000000000..92dabe337d87
--- /dev/null
+++ b/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 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_PERM_FAILED;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.location.timezone.LocationTimeZoneEvent;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+
+import com.android.internal.location.timezone.LocationTimeZoneProviderRequest;
+
+import java.util.Objects;
+
+/**
+ * The real, system-server side implementation of a binder call backed {@link
+ * LocationTimeZoneProvider}. It handles keeping track of current state, timeouts and ensuring
+ * events are passed to the {@link LocationTimeZoneProviderController} on the required thread.
+ */
+class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider {
+
+ private static final String TAG = LocationTimeZoneManagerService.TAG;
+
+ @NonNull private final LocationTimeZoneProviderProxy mProxy;
+
+ BinderLocationTimeZoneProvider(
+ @NonNull ThreadingDomain threadingDomain,
+ @NonNull String providerName,
+ @NonNull LocationTimeZoneProviderProxy proxy) {
+ super(threadingDomain, providerName);
+ mProxy = Objects.requireNonNull(proxy);
+ }
+
+ @Override
+ void onInitialize() {
+ mProxy.setListener(new LocationTimeZoneProviderProxy.Listener() {
+ @Override
+ public void onReportLocationTimeZoneEvent(
+ @NonNull LocationTimeZoneEvent locationTimeZoneEvent) {
+ handleLocationTimeZoneEvent(locationTimeZoneEvent);
+ }
+
+ @Override
+ public void onProviderBound() {
+ handleOnProviderBound();
+ }
+
+ @Override
+ public void onProviderUnbound() {
+ handleProviderLost("onProviderUnbound()");
+ }
+ });
+ }
+
+ private void handleProviderLost(String reason) {
+ mThreadingDomain.assertCurrentThread();
+
+ synchronized (mSharedLock) {
+ ProviderState currentState = mCurrentState.get();
+ switch (currentState.stateEnum) {
+ case PROVIDER_STATE_ENABLED: {
+ // 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
+ ProviderState newState = currentState.newState(
+ PROVIDER_STATE_ENABLED, null, currentState.currentUserConfiguration,
+ msg);
+ setCurrentState(newState, true);
+ break;
+ }
+ case PROVIDER_STATE_DISABLED: {
+ debugLog("handleProviderLost reason=" + reason
+ + ", mProviderName=" + mProviderName
+ + ", currentState=" + currentState
+ + ": No state change required, provider is disabled.");
+ break;
+ }
+ case PROVIDER_STATE_PERM_FAILED: {
+ debugLog("handleProviderLost reason=" + reason
+ + ", mProviderName=" + mProviderName
+ + ", currentState=" + currentState
+ + ": No state change required, provider is perm failed.");
+ break;
+ }
+ default: {
+ throw new IllegalStateException("Unknown currentState=" + currentState);
+ }
+ }
+ }
+ }
+
+ private void handleOnProviderBound() {
+ mThreadingDomain.assertCurrentThread();
+
+ synchronized (mSharedLock) {
+ ProviderState currentState = mCurrentState.get();
+ switch (currentState.stateEnum) {
+ case PROVIDER_STATE_ENABLED: {
+ debugLog("handleOnProviderBound mProviderName=" + mProviderName
+ + ", currentState=" + currentState + ": Provider is enabled.");
+ break;
+ }
+ case PROVIDER_STATE_DISABLED: {
+ debugLog("handleOnProviderBound mProviderName=" + mProviderName
+ + ", currentState=" + currentState + ": Provider is disabled.");
+ break;
+ }
+ case PROVIDER_STATE_PERM_FAILED: {
+ debugLog("handleOnProviderBound"
+ + ", mProviderName=" + mProviderName
+ + ", currentState=" + currentState
+ + ": No state change required, provider is perm failed.");
+ break;
+ }
+ default: {
+ throw new IllegalStateException("Unknown currentState=" + currentState);
+ }
+ }
+ }
+ }
+
+ @Override
+ void onEnable() {
+ // 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)
+ .build();
+ mProxy.setRequest(request);
+ }
+
+ @Override
+ void onDisable() {
+ LocationTimeZoneProviderRequest request =
+ new LocationTimeZoneProviderRequest.Builder()
+ .setReportLocationTimeZone(false)
+ .build();
+ mProxy.setRequest(request);
+ }
+
+ @Override
+ void logWarn(String msg) {
+ Slog.w(TAG, msg);
+ }
+
+ @Override
+ public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
+ synchronized (mSharedLock) {
+ ipw.println("{BinderLocationTimeZoneProvider}");
+ ipw.println("mProviderName=" + mProviderName);
+ ipw.println("mCurrentState=" + mCurrentState);
+ ipw.println("mProxy=" + mProxy);
+
+ ipw.println("State history:");
+ ipw.increaseIndent();
+ mCurrentState.dump(ipw);
+ ipw.decreaseIndent();
+
+ ipw.println("Proxy details:");
+ ipw.increaseIndent();
+ mProxy.dump(ipw, args);
+ ipw.decreaseIndent();
+ }
+ }
+
+ @Override
+ public String toString() {
+ synchronized (mSharedLock) {
+ return "BinderLocationTimeZoneProvider{"
+ + "mProviderName=" + mProviderName
+ + "mCurrentState=" + mCurrentState
+ + "mProxy=" + mProxy
+ + '}';
+ }
+ }
+
+ /**
+ * Passes the supplied simulation / testing event to the current proxy iff the proxy is a
+ * {@link SimulatedLocationTimeZoneProviderProxy}. If not, the event is logged but discarded.
+ */
+ void simulateBinderProviderEvent(SimulatedBinderProviderEvent event) {
+ if (!(mProxy instanceof SimulatedLocationTimeZoneProviderProxy)) {
+ Slog.w(TAG, mProxy + " is not a " + SimulatedLocationTimeZoneProviderProxy.class
+ + ", event=" + event);
+ return;
+ }
+ ((SimulatedLocationTimeZoneProviderProxy) mProxy).simulate(event);
+ }
+}
diff --git a/services/core/java/com/android/server/location/timezone/ControllerCallbackImpl.java b/services/core/java/com/android/server/location/timezone/ControllerCallbackImpl.java
new file mode 100644
index 000000000000..cd9aa2fd6a5b
--- /dev/null
+++ b/services/core/java/com/android/server/location/timezone/ControllerCallbackImpl.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 com.android.server.LocalServices;
+import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
+import com.android.server.timezonedetector.TimeZoneDetectorInternal;
+
+/**
+ * The real implementation of {@link LocationTimeZoneProviderController.Callback} used by
+ * {@link ControllerImpl} to interact with other server components.
+ */
+class ControllerCallbackImpl extends LocationTimeZoneProviderController.Callback {
+
+ ControllerCallbackImpl(@NonNull ThreadingDomain threadingDomain) {
+ super(threadingDomain);
+ }
+
+ @Override
+ void suggest(@NonNull GeolocationTimeZoneSuggestion suggestion) {
+ mThreadingDomain.assertCurrentThread();
+
+ TimeZoneDetectorInternal timeZoneDetector =
+ LocalServices.getService(TimeZoneDetectorInternal.class);
+ timeZoneDetector.suggestGeolocationTimeZone(suggestion);
+ }
+}
diff --git a/services/core/java/com/android/server/location/timezone/ControllerEnvironmentImpl.java b/services/core/java/com/android/server/location/timezone/ControllerEnvironmentImpl.java
new file mode 100644
index 000000000000..2e2481c30f22
--- /dev/null
+++ b/services/core/java/com/android/server/location/timezone/ControllerEnvironmentImpl.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 com.android.server.LocalServices;
+import com.android.server.timezonedetector.ConfigurationInternal;
+import com.android.server.timezonedetector.TimeZoneDetectorInternal;
+
+import java.util.Objects;
+
+/**
+ * The real implementation of {@link LocationTimeZoneProviderController.Environment} used by
+ * {@link ControllerImpl} to interact with other server components.
+ */
+class ControllerEnvironmentImpl extends LocationTimeZoneProviderController.Environment {
+
+ @NonNull private final TimeZoneDetectorInternal mTimeZoneDetectorInternal;
+ @NonNull private final LocationTimeZoneProviderController mController;
+
+ ControllerEnvironmentImpl(@NonNull ThreadingDomain threadingDomain,
+ @NonNull LocationTimeZoneProviderController controller) {
+ super(threadingDomain);
+ mController = Objects.requireNonNull(controller);
+ mTimeZoneDetectorInternal = LocalServices.getService(TimeZoneDetectorInternal.class);
+
+ // Listen for configuration changes.
+ mTimeZoneDetectorInternal.addConfigurationListener(
+ () -> mThreadingDomain.post(mController::onConfigChanged));
+ }
+
+ @Override
+ ConfigurationInternal getCurrentUserConfigurationInternal() {
+ return mTimeZoneDetectorInternal.getCurrentUserConfigurationInternal();
+ }
+}
diff --git a/services/core/java/com/android/server/location/timezone/ControllerImpl.java b/services/core/java/com/android/server/location/timezone/ControllerImpl.java
new file mode 100644
index 000000000000..e31cfc4e8b40
--- /dev/null
+++ b/services/core/java/com/android/server/location/timezone/ControllerImpl.java
@@ -0,0 +1,420 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 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.LocationTimeZoneManagerService.debugLog;
+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_PERM_FAILED;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+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.Objects;
+
+/**
+ * A real implementation of {@link LocationTimeZoneProviderController} that supports a single
+ * {@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.
+ */
+class ControllerImpl extends LocationTimeZoneProviderController {
+
+ @VisibleForTesting
+ static final Duration UNCERTAINTY_DELAY = Duration.ofMinutes(5);
+
+ @NonNull private final LocationTimeZoneProvider mProvider;
+ @NonNull private final SingleRunnableQueue mDelayedSuggestionQueue;
+
+ @GuardedBy("mSharedLock")
+ // Non-null after initialize()
+ private ConfigurationInternal mCurrentUserConfiguration;
+
+ @GuardedBy("mSharedLock")
+ // Non-null after initialize()
+ private Environment mEnvironment;
+
+ @GuardedBy("mSharedLock")
+ // Non-null after initialize()
+ private Callback mCallback;
+
+ /**
+ * Contains any currently pending suggestion on {@link #mDelayedSuggestionQueue}, if there is
+ * one.
+ */
+ @GuardedBy("mSharedLock")
+ @Nullable
+ private GeolocationTimeZoneSuggestion mPendingSuggestion;
+
+ /** Contains the last suggestion actually made, if there is one. */
+ @GuardedBy("mSharedLock")
+ @Nullable
+ private GeolocationTimeZoneSuggestion mLastSuggestion;
+
+ ControllerImpl(@NonNull ThreadingDomain threadingDomain,
+ @NonNull LocationTimeZoneProvider provider) {
+ super(threadingDomain);
+ mDelayedSuggestionQueue = threadingDomain.createSingleRunnableQueue();
+ mProvider = Objects.requireNonNull(provider);
+ }
+
+ @Override
+ void initialize(@NonNull Environment environment, @NonNull Callback callback) {
+ mThreadingDomain.assertCurrentThread();
+
+ synchronized (mSharedLock) {
+ debugLog("initialize()");
+ mEnvironment = Objects.requireNonNull(environment);
+ mCallback = Objects.requireNonNull(callback);
+ mCurrentUserConfiguration = environment.getCurrentUserConfigurationInternal();
+
+ mProvider.initialize(ControllerImpl.this::onProviderStateChange);
+ enableOrDisableProvider(mCurrentUserConfiguration);
+ }
+ }
+
+ @Override
+ void onConfigChanged() {
+ mThreadingDomain.assertCurrentThread();
+
+ synchronized (mSharedLock) {
+ debugLog("onEnvironmentConfigChanged()");
+
+ ConfigurationInternal oldConfig = mCurrentUserConfiguration;
+ ConfigurationInternal newConfig = mEnvironment.getCurrentUserConfigurationInternal();
+ mCurrentUserConfiguration = newConfig;
+
+ 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.
+ debugLog("User changed. old=" + oldConfig.getUserId()
+ + ", new=" + newConfig.getUserId());
+ debugLog("Disabling LocationTimeZoneProviders as needed");
+ if (mProvider.getCurrentState().stateEnum == PROVIDER_STATE_ENABLED) {
+ mProvider.disable();
+ }
+ }
+
+ enableOrDisableProvider(newConfig);
+ }
+ }
+ }
+
+ @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;
+ }
+ } 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;
+ }
+ }
+ }
+
+ 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);
+ }
+ } else {
+ // Clear any queued suggestions.
+ clearDelayedSuggestion();
+
+ // 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.
+ if (mLastSuggestion != null && mLastSuggestion.getZoneIds() != null) {
+ suggestImmediate(createEmptySuggestion(""));
+ }
+ }
+ }
+
+ void onProviderStateChange(@NonNull ProviderState providerState) {
+ mThreadingDomain.assertCurrentThread();
+ assertProviderKnown(providerState.provider);
+
+ synchronized (mSharedLock) {
+ switch (providerState.stateEnum) {
+ case PROVIDER_STATE_DISABLED: {
+ // This should never happen: entering disabled does not trigger an event.
+ warnLog("onProviderStateChange: Unexpected state change for disabled provider,"
+ + " providerState=" + providerState);
+ 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);
+ 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);
+ break;
+ }
+ default: {
+ warnLog("onProviderStateChange: Unexpected providerState=" + providerState);
+ }
+ }
+ }
+ }
+
+ private void assertProviderKnown(LocationTimeZoneProvider provider) {
+ if (provider != mProvider) {
+ 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.
+ */
+ private void providerEnabledProcessEvent(@NonNull ProviderState providerState) {
+ 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);
+ return;
+ }
+
+ // Consistency check for user. This may be possible as there are various races around
+ // current user switches.
+ if (!Objects.equals(event.getUserHandle(), mCurrentUserConfiguration.getUserHandle())) {
+ warnLog("Using event=" + event + " from a different user="
+ + mCurrentUserConfiguration);
+ }
+
+ 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.");
+ }
+
+ switch (event.getEventType()) {
+ case EVENT_TYPE_PERMANENT_FAILURE: {
+ // This shouldn't happen. Providers cannot be enabled and have this event.
+ warnLog("Provider=" + providerState
+ + " is enabled, but event suggests it shouldn't be");
+ break;
+ }
+ case EVENT_TYPE_UNCERTAIN: {
+ scheduleUncertainSuggestionIfNeeded(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);
+ break;
+ }
+ default: {
+ warnLog("Unknown eventType=" + event.getEventType());
+ break;
+ }
+ }
+ }
+
+ /**
+ * 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).
+ */
+ 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);
+ }
+ }
+
+ @Override
+ public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
+ synchronized (mSharedLock) {
+ ipw.println("LocationTimeZoneProviderController:");
+
+ ipw.increaseIndent(); // level 1
+ ipw.println("mCurrentUserConfiguration=" + mCurrentUserConfiguration);
+ ipw.println("mPendingSuggestion=" + mPendingSuggestion);
+ ipw.println("mLastSuggestion=" + mLastSuggestion);
+
+ ipw.println("Provider:");
+ ipw.increaseIndent(); // level 2
+ mProvider.dump(ipw, args);
+ ipw.decreaseIndent(); // level 2
+
+ ipw.decreaseIndent(); // level 1
+ }
+ }
+
+ /** Sends an immediate suggestion, cancelling any pending suggestion. */
+ @GuardedBy("mSharedLock")
+ private void suggestImmediate(@NonNull GeolocationTimeZoneSuggestion suggestion) {
+ debugLog("suggestImmediate: Executing suggestion=" + suggestion);
+ mDelayedSuggestionQueue.runSynchronously(() -> mCallback.suggest(suggestion));
+ mPendingSuggestion = null;
+ mLastSuggestion = suggestion;
+ }
+
+ /** Clears any pending suggestion. */
+ @GuardedBy("mSharedLock")
+ private void clearDelayedSuggestion() {
+ mDelayedSuggestionQueue.cancel();
+ mPendingSuggestion = null;
+ }
+
+
+ /**
+ * 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.
+ */
+ @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;
+ }
+
+ debugLog("suggestDelayed: Scheduling suggestion=" + suggestion);
+ mPendingSuggestion = suggestion;
+
+ mDelayedSuggestionQueue.runDelayed(() -> {
+ debugLog("suggestDelayed: Executing suggestion=" + suggestion);
+ mCallback.suggest(suggestion);
+ mPendingSuggestion = null;
+ mLastSuggestion = suggestion;
+ }, delay.toMillis());
+ }
+
+ private static GeolocationTimeZoneSuggestion createEmptySuggestion(String reason) {
+ GeolocationTimeZoneSuggestion suggestion = new GeolocationTimeZoneSuggestion(null);
+ suggestion.addDebugInfo(reason);
+ return suggestion;
+ }
+
+ /**
+ * 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())) {
+ warnLog("Unable to process simulated binder provider event,"
+ + " unknown providerName in event=" + event);
+ return;
+ }
+ if (!(mProvider instanceof BinderLocationTimeZoneProvider)) {
+ warnLog("Unable to process simulated binder provider event,"
+ + " provider is not a " + BinderLocationTimeZoneProvider.class
+ + ", event=" + event);
+ return;
+ }
+ ((BinderLocationTimeZoneProvider) mProvider).simulateBinderProviderEvent(event);
+ }
+}
diff --git a/services/core/java/com/android/server/location/timezone/HandlerThreadingDomain.java b/services/core/java/com/android/server/location/timezone/HandlerThreadingDomain.java
new file mode 100644
index 000000000000..17e719ed2cb0
--- /dev/null
+++ b/services/core/java/com/android/server/location/timezone/HandlerThreadingDomain.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 com.android.server.location.timezone;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+
+import java.util.Objects;
+
+/**
+ * The real implementation of {@link ThreadingDomain} that uses a {@link Handler}.
+ */
+final class HandlerThreadingDomain extends ThreadingDomain {
+
+ @NonNull private final Handler mHandler;
+
+ HandlerThreadingDomain(Handler handler) {
+ mHandler = Objects.requireNonNull(handler);
+ }
+
+ /**
+ * Returns the {@link Handler} associated with this threading domain. The same {@link Handler}
+ * may be associated with multiple threading domains, e.g. multiple threading domains could
+ * choose to use the {@link com.android.server.FgThread} handler.
+ *
+ * <p>If you find yourself making this public because you need a {@link Handler}, then it may
+ * cause problems with testability. Try to avoid using this method and use methods like {@link
+ * #post(Runnable)} instead.
+ */
+ @NonNull
+ Handler getHandler() {
+ return mHandler;
+ }
+
+ @NonNull
+ Thread getThread() {
+ return getHandler().getLooper().getThread();
+ }
+
+ @Override
+ void post(@NonNull Runnable r) {
+ getHandler().post(r);
+ }
+
+ @Override
+ void postDelayed(@NonNull Runnable r, long delayMillis) {
+ getHandler().postDelayed(r, delayMillis);
+ }
+
+ @Override
+ void postDelayed(Runnable r, Object token, long delayMillis) {
+ getHandler().postDelayed(r, token, delayMillis);
+ }
+
+ @Override
+ void removeQueuedRunnables(Object token) {
+ getHandler().removeCallbacksAndMessages(token);
+ }
+}
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java
new file mode 100644
index 000000000000..238f999ff8a6
--- /dev/null
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java
@@ -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.
+ */
+
+package com.android.server.location.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Binder;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.SystemProperties;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.DumpUtils;
+import com.android.server.FgThread;
+import com.android.server.SystemService;
+import com.android.server.timezonedetector.TimeZoneDetectorInternal;
+import com.android.server.timezonedetector.TimeZoneDetectorService;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Objects;
+
+/**
+ * A service class that acts as a container for the {@link LocationTimeZoneProviderController},
+ * which determines what {@link com.android.server.timezonedetector.GeolocationTimeZoneSuggestion}
+ * 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>Implementation details:
+ *
+ * <p>For simplicity, with the exception of a few outliers like {@link #dump}, all processing in
+ * this service (and package-private helper objects) takes place on a single thread / handler, the
+ * one indicated by {@link ThreadingDomain}. Because methods like {@link #dump} can be invoked on
+ * another thread, the service and its related objects must still be thread-safe.
+ *
+ * <p>For testing / reproduction of bugs, it is possible to put providers into "simulation
+ * mode" where the real binder clients are replaced by {@link
+ * SimulatedLocationTimeZoneProviderProxy}. This means that the real client providers are never
+ * bound (ensuring no real location events will be received) and simulated events / behaviors
+ * can be injected via the command line. To enter simulation mode for a provider, use
+ * "{@code adb shell setprop persist.sys.location_tz_simulation_mode.<provider name> 1}" and reboot.
+ * e.g. "{@code adb shell setprop persist.sys.location_tz_simulation_mode.primary 1}}"
+ * Then use "{@code adb shell cmd location_time_zone_manager help}" for injection. Set the system
+ * properties to "0" and reboot to return to exit simulation mode.
+ */
+public class LocationTimeZoneManagerService extends Binder {
+
+ /**
+ * Controls lifecycle of the {@link LocationTimeZoneManagerService}.
+ */
+ public static class Lifecycle extends SystemService {
+
+ private LocationTimeZoneManagerService mService;
+
+ public Lifecycle(@NonNull Context context) {
+ super(Objects.requireNonNull(context));
+ }
+
+ @Override
+ public void onStart() {
+ if (TimeZoneDetectorService.GEOLOCATION_TIME_ZONE_DETECTION_ENABLED) {
+ Context context = getContext();
+ mService = new LocationTimeZoneManagerService(context);
+
+ // The service currently exposes no LocalService or Binder API, but it extends
+ // Binder and is registered as a binder service so it can receive shell commands.
+ publishBinderService("location_time_zone_manager", mService);
+ } else {
+ Slog.i(TAG, getClass() + " is compile-time disabled");
+ }
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (TimeZoneDetectorService.GEOLOCATION_TIME_ZONE_DETECTION_ENABLED) {
+ if (phase == PHASE_SYSTEM_SERVICES_READY) {
+ // The location service must be functioning after this boot phase.
+ mService.onSystemReady();
+ } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+ // Some providers rely on non-platform code (e.g. gcore), so we wait to
+ // initialize providers until third party code is allowed to run.
+ mService.onSystemThirdPartyAppsCanStart();
+ }
+ }
+ }
+ }
+
+ static final String TAG = "LocationTZDetector";
+
+ static final String PRIMARY_PROVIDER_NAME = "primary";
+
+ private static final String SIMULATION_MODE_SYSTEM_PROPERTY_PREFIX =
+ "persist.sys.location_tz_simulation_mode.";
+
+ private static final String ATTRIBUTION_TAG = "LocationTimeZoneService";
+
+ private static final String PRIMARY_LOCATION_TIME_ZONE_SERVICE_ACTION =
+ "com.android.location.timezone.service.v1.PrimaryLocationTimeZoneProvider";
+
+
+ @NonNull private final Context mContext;
+
+ /**
+ * The {@link ThreadingDomain} used to supply the {@link android.os.Handler} and shared lock
+ * object used by the controller and related components.
+ *
+ * <p>Most operations are executed on the associated handler thread <em>but not all</em>, hence
+ * the requirement for additional synchronization using a shared lock.
+ */
+ @NonNull private final ThreadingDomain mThreadingDomain;
+
+ /** The shared lock from {@link #mThreadingDomain}. */
+ @NonNull private final Object mSharedLock;
+
+ // Lazily initialized. Non-null and effectively final after onSystemThirdPartyAppsCanStart().
+ @GuardedBy("mSharedLock")
+ private ControllerImpl mLocationTimeZoneDetectorController;
+
+ LocationTimeZoneManagerService(Context context) {
+ mContext = context.createAttributionContext(ATTRIBUTION_TAG);
+ mThreadingDomain = new HandlerThreadingDomain(FgThread.getHandler());
+ mSharedLock = mThreadingDomain.getLockObject();
+ }
+
+ void onSystemReady() {
+ // Called on an arbitrary thread during initialization.
+ synchronized (mSharedLock) {
+ // TODO(b/152744911): LocationManagerService watches for packages disappearing. Need to
+ // do anything here?
+
+ // TODO(b/152744911): LocationManagerService watches for foreground app changes. Need to
+ // do anything here?
+ // TODO(b/152744911): LocationManagerService watches screen state. Need to do anything
+ // here?
+ }
+ }
+
+ void onSystemThirdPartyAppsCanStart() {
+ // Called on an arbitrary thread during initialization.
+ synchronized (mSharedLock) {
+ LocationTimeZoneProvider primary = createPrimaryProvider();
+ mLocationTimeZoneDetectorController = new ControllerImpl(mThreadingDomain, primary);
+ ControllerCallbackImpl callback = new ControllerCallbackImpl(mThreadingDomain);
+ ControllerEnvironmentImpl environment = new ControllerEnvironmentImpl(
+ mThreadingDomain, mLocationTimeZoneDetectorController);
+
+ // Initialize the controller on the mThreadingDomain thread: this ensures that the
+ // ThreadingDomain requirements for the controller / environment methods are honored.
+ mThreadingDomain.post(() ->
+ mLocationTimeZoneDetectorController.initialize(environment, callback));
+ }
+ }
+
+ private LocationTimeZoneProvider createPrimaryProvider() {
+ LocationTimeZoneProviderProxy proxy;
+ 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,
+ PRIMARY_LOCATION_TIME_ZONE_SERVICE_ACTION,
+ com.android.internal.R.bool.config_enablePrimaryLocationTimeZoneOverlay,
+ com.android.internal.R.string.config_primaryLocationTimeZoneProviderPackageName
+ );
+ */
+ }
+ return createLocationTimeZoneProvider(PRIMARY_PROVIDER_NAME, proxy);
+ }
+
+ private boolean isInSimulationMode(String providerName) {
+ return SystemProperties.getBoolean(
+ SIMULATION_MODE_SYSTEM_PROPERTY_PREFIX + providerName, false);
+ }
+
+ private LocationTimeZoneProvider createLocationTimeZoneProvider(
+ @NonNull String providerName, @NonNull LocationTimeZoneProviderProxy proxy) {
+ LocationTimeZoneProvider provider;
+ if (proxy != null) {
+ debugLog("LocationTimeZoneProvider found for providerName=" + providerName);
+ provider = new BinderLocationTimeZoneProvider(mThreadingDomain,
+ providerName, proxy);
+ } else {
+ debugLog("No LocationTimeZoneProvider found for providerName=" + providerName
+ + ": stubbing");
+ provider = new NullLocationTimeZoneProvider(mThreadingDomain, providerName);
+ }
+ return provider;
+ }
+
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out,
+ FileDescriptor err, String[] args, ShellCallback callback,
+ ResultReceiver resultReceiver) {
+ (new LocationTimeZoneManagerShellCommand(this)).exec(
+ this, in, out, err, args, callback, resultReceiver);
+ }
+
+ /**
+ * Asynchronously passes a {@link SimulatedBinderProviderEvent] to the appropriate provider.
+ * The device must be in simulation mode, otherwise an {@link IllegalStateException} will be
+ * thrown.
+ */
+ void simulateBinderProviderEvent(SimulatedBinderProviderEvent event)
+ throws IllegalStateException {
+ if (!isInSimulationMode(event.getProviderName())) {
+ throw new IllegalStateException("Use \"setprop "
+ + SIMULATION_MODE_SYSTEM_PROPERTY_PREFIX + event.getProviderName()
+ + " 1\" and reboot before injecting simulated binder events.");
+ }
+ mThreadingDomain.post(
+ () -> mLocationTimeZoneDetectorController.simulateBinderProviderEvent(event));
+ }
+
+ @Override
+ protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
+ @Nullable String[] args) {
+ if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+ // Called on an arbitrary thread at any time.
+ synchronized (mSharedLock) {
+ ipw.println("LocationTimeZoneManagerService:");
+ ipw.increaseIndent();
+ if (mLocationTimeZoneDetectorController == null) {
+ ipw.println("{Uninitialized}");
+ } else {
+ mLocationTimeZoneDetectorController.dump(ipw, args);
+ }
+ ipw.decreaseIndent();
+ }
+ }
+
+ static void debugLog(String msg) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Slog.d(TAG, msg);
+ }
+ }
+
+ static void warnLog(String msg) {
+ if (Log.isLoggable(TAG, Log.WARN)) {
+ Slog.w(TAG, msg);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerShellCommand.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerShellCommand.java
new file mode 100644
index 000000000000..7c3b891743cc
--- /dev/null
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerShellCommand.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.server.location.timezone;
+
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+/** Implements the shell command interface for {@link LocationTimeZoneManagerService}. */
+class LocationTimeZoneManagerShellCommand extends ShellCommand {
+
+ private final LocationTimeZoneManagerService mService;
+
+ LocationTimeZoneManagerShellCommand(LocationTimeZoneManagerService service) {
+ mService = service;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+
+ switch (cmd) {
+ case "simulate_binder": {
+ return runSimulateBinderEvent();
+ }
+ default: {
+ return handleDefaultCommands(cmd);
+ }
+ }
+ }
+
+ private int runSimulateBinderEvent() {
+ PrintWriter outPrintWriter = getOutPrintWriter();
+
+ SimulatedBinderProviderEvent simulatedProviderBinderEvent;
+ try {
+ simulatedProviderBinderEvent = SimulatedBinderProviderEvent.createFromArgs(this);
+ } catch (IllegalArgumentException e) {
+ outPrintWriter.println("Error: " + e.getMessage());
+ return 1;
+ }
+
+ outPrintWriter.println("Injecting: " + simulatedProviderBinderEvent);
+ try {
+ mService.simulateBinderProviderEvent(simulatedProviderBinderEvent);
+ } catch (IllegalStateException e) {
+ outPrintWriter.println("Error: " + e.getMessage());
+ return 2;
+ }
+ return 0;
+ }
+
+ @Override
+ public void onHelp() {
+ final PrintWriter pw = getOutPrintWriter();
+ pw.println("Location Time Zone Manager (location_time_zone_manager) commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println(" simulate_binder");
+ pw.println(" <simulated provider binder event>");
+ pw.println();
+ SimulatedBinderProviderEvent.printCommandLineOpts(pw);
+ pw.println();
+ }
+}
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java
new file mode 100644
index 000000000000..3743779b20c9
--- /dev/null
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java
@@ -0,0 +1,511 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 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.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_PERM_FAILED;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.location.timezone.LocationTimeZoneEvent;
+import android.os.Handler;
+import android.os.SystemClock;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.timezonedetector.ConfigurationInternal;
+import com.android.server.timezonedetector.Dumpable;
+import com.android.server.timezonedetector.ReferenceWithHistory;
+
+import java.util.Objects;
+
+/**
+ * A facade used by the {@link LocationTimeZoneProviderController} to interact with a location time
+ * zone provider. The provider could have a binder implementation with logic running in another
+ * process, or could be a stubbed instance when no real provider is registered.
+ *
+ * <p>The provider is supplied with a {@link ProviderListener} via {@link
+ * #initialize(ProviderListener)}. This enables it to communicates asynchronous detection / error
+ * events back to the {@link LocationTimeZoneProviderController} via the {@link
+ * ProviderListener#onProviderStateChange} method. This call must be made on the
+ * {@link Handler} thread from the {@link ThreadingDomain} passed to the constructor.
+ *
+ * <p>All incoming calls from the controller except for {@link
+ * LocationTimeZoneProvider#dump(android.util.IndentingPrintWriter, String[])} will be made on the
+ * {@link Handler} thread of the {@link ThreadingDomain} passed to the constructor.
+ */
+abstract class LocationTimeZoneProvider implements Dumpable {
+
+ /**
+ * Listener interface used by the {@link LocationTimeZoneProviderController} to register an
+ * interest in provider events.
+ */
+ interface ProviderListener {
+ /**
+ * Indicated that a provider changed states. The {@code providerState} indicates which one
+ */
+ void onProviderStateChange(@NonNull ProviderState providerState);
+ }
+
+ /**
+ * Information about the provider's current state.
+ */
+ static class ProviderState {
+
+ @IntDef({ PROVIDER_STATE_UNKNOWN, PROVIDER_STATE_ENABLED, PROVIDER_STATE_DISABLED,
+ PROVIDER_STATE_PERM_FAILED })
+ @interface ProviderStateEnum {}
+
+ /**
+ * Uninitialized value. Must not be used afte {@link LocationTimeZoneProvider#initialize}.
+ */
+ static final int PROVIDER_STATE_UNKNOWN = 0;
+
+ /**
+ * The provider is currently enabled.
+ */
+ static final int PROVIDER_STATE_ENABLED = 1;
+
+ /**
+ * The provider is currently disabled.
+ * This is the state after {@link #initialize} is called.
+ */
+ static final int PROVIDER_STATE_DISABLED = 2;
+
+ /**
+ * 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;
+
+ /** The {@link LocationTimeZoneProvider} the state is for. */
+ public final @NonNull LocationTimeZoneProvider provider;
+
+ /** The state enum value of the current state. */
+ public final @ProviderStateEnum int stateEnum;
+
+ /**
+ * 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
+ * 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}.
+ */
+ @Nullable public final ConfigurationInternal currentUserConfiguration;
+
+ /**
+ * The time according to the elapsed realtime clock when the provider entered the current
+ * state. Included for debugging, not used for equality.
+ */
+ private final long mStateEntryTimeMillis;
+
+ /**
+ * Debug information providing context for the transition to this state. Included for
+ * debugging, not used for equality.
+ */
+ @Nullable private final String mDebugInfo;
+
+
+ private ProviderState(@NonNull LocationTimeZoneProvider provider,
+ @ProviderStateEnum int stateEnum, @Nullable LocationTimeZoneEvent event,
+ @Nullable ConfigurationInternal currentUserConfiguration,
+ @Nullable String debugInfo) {
+ this.provider = Objects.requireNonNull(provider);
+ this.stateEnum = stateEnum;
+ this.event = event;
+ this.currentUserConfiguration = currentUserConfiguration;
+ this.mStateEntryTimeMillis = SystemClock.elapsedRealtime();
+ this.mDebugInfo = debugInfo;
+ }
+
+ /** Creates the bootstrap state, uses {@link #PROVIDER_STATE_UNKNOWN}. */
+ static ProviderState createStartingState(
+ @NonNull LocationTimeZoneProvider provider) {
+ return new ProviderState(
+ provider, PROVIDER_STATE_UNKNOWN, null, null, "Initial state");
+ }
+
+ /**
+ * Create a new state from this state. Validates that the state transition is valid
+ * and that the required parameters for the new state are present / absent.
+ */
+ ProviderState newState(@ProviderStateEnum int newStateEnum,
+ @Nullable LocationTimeZoneEvent event,
+ @Nullable ConfigurationInternal currentUserConfig,
+ @Nullable String debugInfo) {
+
+ // Check valid "from" transitions.
+ switch (this.stateEnum) {
+ case PROVIDER_STATE_UNKNOWN: {
+ if (newStateEnum != PROVIDER_STATE_DISABLED) {
+ throw new IllegalArgumentException(
+ "Must transition from " + prettyPrintStateEnum(
+ PROVIDER_STATE_UNKNOWN)
+ + " to " + prettyPrintStateEnum(PROVIDER_STATE_DISABLED));
+ }
+ break;
+ }
+ case PROVIDER_STATE_DISABLED:
+ case PROVIDER_STATE_ENABLED: {
+ // These can go to each other or PROVIDER_STATE_PERM_FAILED.
+ break;
+ }
+ case PROVIDER_STATE_PERM_FAILED: {
+ throw new IllegalArgumentException("Illegal transition out of "
+ + prettyPrintStateEnum(PROVIDER_STATE_UNKNOWN));
+ }
+ default: {
+ throw new IllegalArgumentException("Invalid this.stateEnum=" + this.stateEnum);
+ }
+ }
+
+ // Validate "to" transitions / arguments.
+ switch (newStateEnum) {
+ case PROVIDER_STATE_UNKNOWN: {
+ throw new IllegalArgumentException("Cannot transition to "
+ + prettyPrintStateEnum(PROVIDER_STATE_UNKNOWN));
+ }
+ case PROVIDER_STATE_DISABLED: {
+ if (event != null || currentUserConfig != null) {
+ throw new IllegalArgumentException(
+ "Disabled state: event and currentUserConfig must be null"
+ + ", event=" + event
+ + ", currentUserConfig=" + currentUserConfig);
+ }
+ break;
+ }
+ case PROVIDER_STATE_ENABLED: {
+ if (currentUserConfig == null) {
+ throw new IllegalArgumentException(
+ "Enabled state: currentUserConfig must not be null");
+ }
+ break;
+ }
+ case PROVIDER_STATE_PERM_FAILED: {
+ if (event != null || currentUserConfig != null) {
+ throw new IllegalArgumentException(
+ "Perf failed state: event and currentUserConfig must be null"
+ + ", event=" + event
+ + ", currentUserConfig=" + currentUserConfig);
+ }
+ break;
+ }
+ default: {
+ throw new IllegalArgumentException("Unknown newStateEnum=" + newStateEnum);
+ }
+ }
+ return new ProviderState(provider, newStateEnum, event, currentUserConfig, debugInfo);
+ }
+
+ @Override
+ public String toString() {
+ return "State{"
+ + "stateEnum=" + prettyPrintStateEnum(stateEnum)
+ + ", event=" + event
+ + ", currentUserConfiguration=" + currentUserConfiguration
+ + ", mStateEntryTimeMillis=" + mStateEntryTimeMillis
+ + ", mDebugInfo=" + mDebugInfo
+ + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ProviderState state = (ProviderState) o;
+ return stateEnum == state.stateEnum
+ && Objects.equals(event, state.event)
+ && Objects.equals(currentUserConfiguration, state.currentUserConfiguration);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(stateEnum, event, currentUserConfiguration);
+ }
+
+ private static String prettyPrintStateEnum(@ProviderStateEnum int state) {
+ switch (state) {
+ case PROVIDER_STATE_DISABLED:
+ return "Disabled (" + PROVIDER_STATE_DISABLED + ")";
+ case PROVIDER_STATE_ENABLED:
+ return "Enabled (" + PROVIDER_STATE_ENABLED + ")";
+ case PROVIDER_STATE_PERM_FAILED:
+ return "Perm failure (" + PROVIDER_STATE_PERM_FAILED + ")";
+ case PROVIDER_STATE_UNKNOWN:
+ default:
+ return "Unknown (" + state + ")";
+ }
+ }
+ }
+
+ @NonNull final ThreadingDomain mThreadingDomain;
+ @NonNull final Object mSharedLock;
+ @NonNull final String mProviderName;
+
+ /**
+ * The current state (with history for debugging).
+ */
+ @GuardedBy("mSharedLock")
+ final ReferenceWithHistory<ProviderState> mCurrentState =
+ new ReferenceWithHistory<>(10);
+
+ // Non-null and effectively final after initialize() is called.
+ ProviderListener mProviderListener;
+
+ /** Creates the instance. */
+ LocationTimeZoneProvider(@NonNull ThreadingDomain threadingDomain,
+ @NonNull String providerName) {
+ mThreadingDomain = Objects.requireNonNull(threadingDomain);
+ mSharedLock = threadingDomain.getLockObject();
+ mProviderName = Objects.requireNonNull(providerName);
+ }
+
+ /**
+ * Called before the provider is first used.
+ */
+ final void initialize(@NonNull ProviderListener providerListener) {
+ mThreadingDomain.assertCurrentThread();
+
+ synchronized (mSharedLock) {
+ if (mProviderListener != null) {
+ throw new IllegalStateException("initialize already called");
+ }
+ mProviderListener = Objects.requireNonNull(providerListener);
+ ProviderState currentState = ProviderState.createStartingState(this);
+ ProviderState newState = currentState.newState(
+ PROVIDER_STATE_DISABLED, null, null, "initialize() called");
+ setCurrentState(newState, false);
+
+ onInitialize();
+ }
+ }
+
+ /**
+ * Implemented by subclasses to do work during {@link #initialize}.
+ */
+ abstract void onInitialize();
+
+ /**
+ * Set the current state, for use by this class and subclasses only. If {@code #notifyChanges}
+ * is {@code true} and {@code newState} is not equal to the old state, then {@link
+ * ProviderListener#onProviderStateChange(ProviderState)} must be called on
+ * {@link #mProviderListener}.
+ */
+ final void setCurrentState(@NonNull ProviderState newState, boolean notifyChanges) {
+ mThreadingDomain.assertCurrentThread();
+ synchronized (mSharedLock) {
+ ProviderState oldState = mCurrentState.get();
+ mCurrentState.set(newState);
+ onSetCurrentState(newState);
+ if (notifyChanges) {
+ if (!Objects.equals(newState, oldState)) {
+ mProviderListener.onProviderStateChange(newState);
+ }
+ }
+ }
+ }
+
+ /**
+ * Overridden by subclasses to do work during {@link #setCurrentState}.
+ */
+ @GuardedBy("mSharedLock")
+ void onSetCurrentState(ProviderState newState) {
+ // Default no-op.
+ }
+
+ /**
+ * Returns the current state of the provider. This method must be called using the handler
+ * thread from the {@link ThreadingDomain}.
+ */
+ @NonNull
+ final ProviderState getCurrentState() {
+ mThreadingDomain.assertCurrentThread();
+ synchronized (mSharedLock) {
+ return mCurrentState.get();
+ }
+ }
+
+ /**
+ * Returns the name of the provider. This method must be called using the handler thread from
+ * the {@link ThreadingDomain}.
+ */
+ final String getName() {
+ mThreadingDomain.assertCurrentThread();
+ return mProviderName;
+ }
+
+ /**
+ * Enables the provider. It is an error to call this method except when the {@link
+ * #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) {
+ mThreadingDomain.assertCurrentThread();
+
+ synchronized (mSharedLock) {
+ assertCurrentState(PROVIDER_STATE_DISABLED);
+
+ ProviderState currentState = getCurrentState();
+ ProviderState newState = currentState.newState(
+ PROVIDER_STATE_ENABLED, null, currentUserConfiguration, "enable() called");
+ setCurrentState(newState, false);
+ onEnable();
+ }
+ }
+
+ /**
+ * Implemented by subclasses to do work during {@link #enable}.
+ */
+ abstract void onEnable();
+
+ /**
+ * 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
+ * called using the handler thread from the {@link ThreadingDomain}.
+ */
+ final void disable() {
+ mThreadingDomain.assertCurrentThread();
+
+ synchronized (mSharedLock) {
+ assertCurrentState(PROVIDER_STATE_ENABLED);
+
+ ProviderState currentState = getCurrentState();
+ ProviderState newState =
+ currentState.newState(PROVIDER_STATE_DISABLED, null, null, "disable() called");
+ setCurrentState(newState, false);
+
+ onDisable();
+ }
+ }
+
+ /**
+ * Implemented by subclasses to do work during {@link #disable}.
+ */
+ abstract void onDisable();
+
+ /** For subclasses to invoke when a {@link LocationTimeZoneEvent} has been received. */
+ final void handleLocationTimeZoneEvent(
+ @NonNull LocationTimeZoneEvent locationTimeZoneEvent) {
+ mThreadingDomain.assertCurrentThread();
+ Objects.requireNonNull(locationTimeZoneEvent);
+
+ synchronized (mSharedLock) {
+ debugLog("handleLocationTimeZoneEvent: mProviderName=" + mProviderName
+ + ", locationTimeZoneEvent=" + locationTimeZoneEvent);
+
+ ProviderState currentState = getCurrentState();
+ int eventType = locationTimeZoneEvent.getEventType();
+ switch (currentState.stateEnum) {
+ case PROVIDER_STATE_PERM_FAILED: {
+ // After entering perm failed, there is nothing to do. The remote peer is
+ // supposed to stop sending events after it has reported perm failure.
+ logWarn("handleLocationTimeZoneEvent: Event=" + locationTimeZoneEvent
+ + " received for provider=" + this + " when in failed state");
+ return;
+ }
+ case PROVIDER_STATE_DISABLED: {
+ switch (eventType) {
+ case EVENT_TYPE_PERMANENT_FAILURE: {
+ String msg = "handleLocationTimeZoneEvent:"
+ + " Failure event=" + locationTimeZoneEvent
+ + " received for disabled provider=" + this
+ + ", entering permanently failed state";
+ logWarn(msg);
+ ProviderState newState = currentState.newState(
+ PROVIDER_STATE_PERM_FAILED, null, null, msg);
+ setCurrentState(newState, true);
+ return;
+ }
+ case EVENT_TYPE_SUCCESS:
+ case EVENT_TYPE_UNCERTAIN: {
+ // Any geolocation-related events received for a disabled provider are
+ // ignored: they should not happen.
+ logWarn("handleLocationTimeZoneEvent:"
+ + " event=" + locationTimeZoneEvent
+ + " received for disabled provider=" + this
+ + ", ignoring");
+
+ return;
+ }
+ default: {
+ throw new IllegalStateException(
+ "Unknown eventType=" + locationTimeZoneEvent);
+ }
+ }
+ }
+ case PROVIDER_STATE_ENABLED: {
+ switch (eventType) {
+ case EVENT_TYPE_PERMANENT_FAILURE: {
+ String msg = "handleLocationTimeZoneEvent:"
+ + " Failure event=" + locationTimeZoneEvent
+ + " received for provider=" + this
+ + ", entering permanently failed state";
+ logWarn(msg);
+ ProviderState newState = currentState.newState(
+ PROVIDER_STATE_PERM_FAILED, null, null, msg);
+ setCurrentState(newState, true);
+ return;
+ }
+ case EVENT_TYPE_UNCERTAIN:
+ case EVENT_TYPE_SUCCESS: {
+ ProviderState newState = currentState.newState(PROVIDER_STATE_ENABLED,
+ locationTimeZoneEvent, currentState.currentUserConfiguration,
+ "handleLocationTimeZoneEvent() when enabled");
+ setCurrentState(newState, true);
+ return;
+ }
+ default: {
+ throw new IllegalStateException(
+ "Unknown eventType=" + locationTimeZoneEvent);
+ }
+ }
+ }
+ default: {
+ throw new IllegalStateException("Unknown providerType=" + currentState);
+ }
+ }
+ }
+ }
+
+ /**
+ * Implemented by subclasses.
+ */
+ abstract void logWarn(String msg);
+
+ private void assertCurrentState(@ProviderState.ProviderStateEnum int requiredState) {
+ ProviderState currentState = getCurrentState();
+ if (currentState.stateEnum != requiredState) {
+ throw new IllegalStateException(
+ "Required stateEnum=" + requiredState + ", but was " + currentState);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java
new file mode 100644
index 000000000000..2f75c43594df
--- /dev/null
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.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 com.android.server.location.timezone;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+
+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.util.Objects;
+
+/**
+ * An base class for the component responsible handling events from {@link
+ * LocationTimeZoneProvider}s and synthesizing time zone ID suggestions for sending to the time zone
+ * detector. This interface primarily exists to extract testable detection logic, i.e. with
+ * a minimal number of threading considerations or dependencies on Android infrastructure.
+ *
+ * <p>The controller interacts with the following components:
+ * <ul>
+ * <li>The surrounding service, which calls {@link #initialize(Environment, Callback)} and
+ * {@link #onConfigChanged()}.</li>
+ * <li>The {@link Environment} through which obtains information it needs.</li>
+ * <li>The {@link Callback} through which it makes time zone suggestions.</li>
+ * <li>Any {@link LocationTimeZoneProvider} instances it owns, which communicate via the
+ * {@link LocationTimeZoneProvider.ProviderListener#onProviderStateChange(ProviderState)}
+ * method.</li>
+ * </ul>
+ *
+ * <p>All incoming calls except for {@link
+ * LocationTimeZoneProviderController#dump(android.util.IndentingPrintWriter, String[])} must be
+ * made on the {@link Handler} thread of the {@link ThreadingDomain} passed to {@link
+ * #LocationTimeZoneProviderController(ThreadingDomain)}.
+ *
+ * <p>Provider / controller integration notes:
+ *
+ * <p>Providers distinguish between "unknown unknowns" ("uncertain") and "known unknowns"
+ * ("certain"), i.e. a provider can be uncertain and not know what the time zone is, which is
+ * different from the certainty that there are no time zone IDs for the current location. A provider
+ * can be certain about there being no time zone IDs for a location for good reason, e.g. for
+ * disputed areas and oceans. Distinguishing uncertainty allows the controller to try other
+ * providers (or give up), where as certainty means it should not.
+ *
+ * <p>A provider can fail permanently. A permanent failure will disable the provider until next
+ * boot.
+ */
+abstract class LocationTimeZoneProviderController implements Dumpable {
+
+ @NonNull protected final ThreadingDomain mThreadingDomain;
+ @NonNull protected final Object mSharedLock;
+
+ LocationTimeZoneProviderController(@NonNull ThreadingDomain threadingDomain) {
+ mThreadingDomain = Objects.requireNonNull(threadingDomain);
+ mSharedLock = threadingDomain.getLockObject();
+ }
+
+ /**
+ * Called to initialize the controller during boot. Called once only.
+ * {@link LocationTimeZoneProvider#initialize} must be called by this method.
+ */
+ abstract void initialize(@NonNull Environment environment, @NonNull Callback callback);
+
+ /**
+ * Called when any settings or other device state that affect location-based time zone detection
+ * have changed. The receiver should call {@link
+ * Environment#getCurrentUserConfigurationInternal()} to get the current user's config. This
+ * call must be made on the {@link ThreadingDomain} handler thread.
+ */
+ abstract void onConfigChanged();
+
+ /**
+ * Used by {@link LocationTimeZoneProviderController} to obtain information from the surrounding
+ * service. It can easily be faked for tests.
+ */
+ abstract static class Environment {
+
+ @NonNull protected final ThreadingDomain mThreadingDomain;
+ @NonNull protected final Object mSharedLock;
+
+ Environment(@NonNull ThreadingDomain threadingDomain) {
+ mThreadingDomain = Objects.requireNonNull(threadingDomain);
+ mSharedLock = threadingDomain.getLockObject();
+ }
+
+ /** Returns the {@link ConfigurationInternal} for the current user of the device. */
+ abstract ConfigurationInternal getCurrentUserConfigurationInternal();
+ }
+
+ /**
+ * Used by {@link LocationTimeZoneProviderController} to interact with the surrounding service.
+ * It can easily be faked for tests.
+ */
+ abstract static class Callback {
+
+ @NonNull protected final ThreadingDomain mThreadingDomain;
+ @NonNull protected final Object mSharedLock;
+
+ Callback(@NonNull ThreadingDomain threadingDomain) {
+ mThreadingDomain = Objects.requireNonNull(threadingDomain);
+ mSharedLock = threadingDomain.getLockObject();
+ }
+
+ /**
+ * Suggests the latest time zone state for the device.
+ */
+ abstract void suggest(@NonNull GeolocationTimeZoneSuggestion suggestion);
+ }
+}
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderProxy.java
new file mode 100644
index 000000000000..3d889ae1856a
--- /dev/null
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderProxy.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.Context;
+import android.location.timezone.LocationTimeZoneEvent;
+import android.os.Handler;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.location.timezone.LocationTimeZoneProviderRequest;
+import com.android.server.timezonedetector.Dumpable;
+
+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 LocationTimeZoneProviders are bound / unbound
+ * this proxy will rebind to the "best" available remote process.
+ *
+ * <p>Threading guarantees provided / required by this interface:
+ * <ul>
+ * <li>All public methods defined by this class must be invoked using the {@link Handler} thread
+ * from the {@link ThreadingDomain} passed to the constructor, excluding
+ * {@link #dump(IndentingPrintWriter, String[])}</li>
+ * <li>Non-static public methods that make binder calls to remote processes (e.g.
+ * {@link #setRequest(LocationTimeZoneProviderRequest)}) are executed asynchronously and will
+ * return immediately.</li>
+ * <li>Callbacks received via binder are delivered via {@link Listener} are delivered on the
+ * {@link Handler} thread from the {@link ThreadingDomain} passed to the constructor.
+ * </ul>
+ *
+ * <p>This class exists to enable the introduction of test implementations of {@link
+ * LocationTimeZoneProviderProxy} that can be used when a device is in a test mode to inject test
+ * events / behavior that are otherwise difficult to simulate.
+ */
+abstract class LocationTimeZoneProviderProxy implements Dumpable {
+
+ @NonNull protected final Context mContext;
+ @NonNull protected final ThreadingDomain mThreadingDomain;
+ @NonNull protected final Object mSharedLock;
+
+ // Non-null and effectively final after setListener() is called.
+ @GuardedBy("mSharedLock")
+ @Nullable
+ protected Listener mListener;
+
+ LocationTimeZoneProviderProxy(
+ @NonNull Context context, @NonNull ThreadingDomain threadingDomain) {
+ mContext = Objects.requireNonNull(context);
+ mThreadingDomain = Objects.requireNonNull(threadingDomain);
+ mSharedLock = threadingDomain.getLockObject();
+ }
+
+ /**
+ * Sets the listener. The listener can expect to receive all events after this point.
+ */
+ void setListener(@NonNull Listener listener) {
+ Objects.requireNonNull(listener);
+ synchronized (mSharedLock) {
+ if (mListener != null) {
+ throw new IllegalStateException("listener already set");
+ }
+ this.mListener = listener;
+ }
+ }
+
+ /**
+ * Sets a new request for the provider.
+ */
+ abstract void setRequest(@NonNull LocationTimeZoneProviderRequest request);
+
+ /**
+ * Handles a {@link LocationTimeZoneEvent} from a remote process.
+ */
+ final void handleLocationTimeZoneEvent(
+ @NonNull LocationTimeZoneEvent locationTimeZoneEvent) {
+ // These calls are invoked on a binder thread. Move to the mThreadingDomain thread as
+ // required by the guarantees for this class.
+ mThreadingDomain.post(() -> mListener.onReportLocationTimeZoneEvent(locationTimeZoneEvent));
+ }
+
+ /**
+ * Interface for listening to location time zone providers. See {@link
+ * LocationTimeZoneProviderProxy} for threading guarantees.
+ */
+ interface Listener {
+
+ /**
+ * Called when a provider receives a {@link LocationTimeZoneEvent}.
+ */
+ void onReportLocationTimeZoneEvent(@NonNull LocationTimeZoneEvent locationTimeZoneEvent);
+
+ /**
+ * Called when a provider is (re)bound.
+ */
+ void onProviderBound();
+
+ /** Called when a provider is unbound. */
+ void onProviderUnbound();
+ }
+}
diff --git a/services/core/java/com/android/server/location/timezone/NullLocationTimeZoneProvider.java b/services/core/java/com/android/server/location/timezone/NullLocationTimeZoneProvider.java
new file mode 100644
index 000000000000..79e2b975089d
--- /dev/null
+++ b/services/core/java/com/android/server/location/timezone/NullLocationTimeZoneProvider.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.timezone;
+
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+
+/**
+ * A {@link LocationTimeZoneProvider} that provides minimal responses needed for the {@link
+ * LocationTimeZoneProviderController} to operate correctly when there is no "real" provider
+ * configured. This can be used during development / testing, or in a production build when the
+ * platform supports more providers than are needed for an Android deployment.
+ *
+ * <p>For example, if the {@link LocationTimeZoneProviderController} supports a primary
+ * and a secondary {@link LocationTimeZoneProvider}, but only a primary is configured, the secondary
+ * config will be left null and the {@link LocationTimeZoneProvider} implementation will be
+ * defaulted to a {@link NullLocationTimeZoneProvider}. The {@link NullLocationTimeZoneProvider}
+ * enters a {@link ProviderState#PROVIDER_STATE_PERM_FAILED} state immediately after being enabled
+ * for the first time and sends the appropriate event, which ensures the {@link
+ * LocationTimeZoneProviderController} won't expect any further {@link
+ * android.location.timezone.LocationTimeZoneEvent}s to come from it, and won't attempt to use it
+ * again.
+ */
+class NullLocationTimeZoneProvider extends LocationTimeZoneProvider {
+
+ private static final String TAG = "NullLocationTimeZoneProvider";
+
+ /** Creates the instance. */
+ NullLocationTimeZoneProvider(@NonNull ThreadingDomain threadingDomain,
+ @NonNull String providerName) {
+ super(threadingDomain, providerName);
+ }
+
+ @Override
+ void onInitialize() {
+ // No-op
+ }
+
+ @Override
+ void onEnable() {
+ // Report a failure (asynchronously using the mThreadingDomain thread to avoid recursion).
+ mThreadingDomain.post(()-> {
+ // Enter the perm-failed state.
+ ProviderState currentState = mCurrentState.get();
+ ProviderState failedState = currentState.newState(
+ PROVIDER_STATE_PERM_FAILED, null, null, "Stubbed provider");
+ setCurrentState(failedState, true);
+ });
+ }
+
+ @Override
+ void onDisable() {
+ // Ignored - NullLocationTimeZoneProvider is always permanently failed.
+ }
+
+ @Override
+ void logWarn(String msg) {
+ Slog.w(TAG, msg);
+ }
+
+ @Override
+ public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
+ synchronized (mSharedLock) {
+ ipw.println("{Stubbed LocationTimeZoneProvider}");
+ ipw.println("mProviderName=" + mProviderName);
+ ipw.println("mCurrentState=" + mCurrentState);
+ }
+ }
+
+ @Override
+ public String toString() {
+ synchronized (mSharedLock) {
+ return "NullLocationTimeZoneProvider{"
+ + "mProviderName='" + mProviderName + '\''
+ + "mCurrentState='" + mCurrentState + '\''
+ + '}';
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/location/timezone/SimulatedBinderProviderEvent.java b/services/core/java/com/android/server/location/timezone/SimulatedBinderProviderEvent.java
new file mode 100644
index 000000000000..ef2e349fceaa
--- /dev/null
+++ b/services/core/java/com/android/server/location/timezone/SimulatedBinderProviderEvent.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 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.LocationTimeZoneManagerService.PRIMARY_PROVIDER_NAME;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.location.timezone.LocationTimeZoneEvent;
+import android.os.ShellCommand;
+import android.os.SystemClock;
+import android.os.UserHandle;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * An event used for simulating real binder proxy behavior using a {@link
+ * SimulatedLocationTimeZoneProviderProxy}.
+ */
+final class SimulatedBinderProviderEvent {
+
+ private static final List<String> VALID_PROVIDER_NAMES = Arrays.asList(PRIMARY_PROVIDER_NAME);
+
+ static final int INJECTED_EVENT_TYPE_ON_BIND = 1;
+ static final int INJECTED_EVENT_TYPE_ON_UNBIND = 2;
+ static final int INJECTED_EVENT_TYPE_LOCATION_TIME_ZONE_EVENT = 3;
+
+
+ @NonNull private final String mProviderName;
+ private final int mEventType;
+ @Nullable private final LocationTimeZoneEvent mLocationTimeZoneEvent;
+
+ private SimulatedBinderProviderEvent(@NonNull String providerName, int eventType,
+ @Nullable LocationTimeZoneEvent locationTimeZoneEvent) {
+ this.mProviderName = Objects.requireNonNull(providerName);
+ this.mEventType = eventType;
+ this.mLocationTimeZoneEvent = locationTimeZoneEvent;
+ }
+
+ @NonNull
+ String getProviderName() {
+ return mProviderName;
+ }
+
+ @Nullable
+ LocationTimeZoneEvent getLocationTimeZoneEvent() {
+ return mLocationTimeZoneEvent;
+ }
+
+ int getEventType() {
+ return mEventType;
+ }
+
+ /** Prints the command line options that {@link #createFromArgs(ShellCommand)} understands. */
+ static void printCommandLineOpts(PrintWriter pw) {
+ pw.println("Simulated provider binder event:");
+ pw.println();
+ pw.println("<provider name> [onBind|onUnbind|locationTimeZoneEvent"
+ + " <location time zone event args>]");
+ pw.println();
+ pw.println("<provider name> = " + VALID_PROVIDER_NAMES);
+ pw.println("<location time zone event args> ="
+ + " [PERMANENT_FAILURE|UNCERTAIN|SUCCESS <time zone ids>*]");
+ }
+
+ /**
+ * Constructs a {@link SimulatedBinderProviderEvent} from the arguments of {@code shellCommand}.
+ */
+ static SimulatedBinderProviderEvent createFromArgs(ShellCommand shellCommand) {
+ String providerName = shellCommand.getNextArgRequired();
+ if (!VALID_PROVIDER_NAMES.contains(providerName)) {
+ throw new IllegalArgumentException("Unknown provider name=" + providerName);
+ }
+ String injectedEvent = shellCommand.getNextArgRequired();
+ switch (injectedEvent) {
+ case "onBind": {
+ return new SimulatedBinderProviderEvent(
+ providerName, INJECTED_EVENT_TYPE_ON_BIND, null);
+ }
+ case "onUnbind": {
+ return new SimulatedBinderProviderEvent(
+ providerName, INJECTED_EVENT_TYPE_ON_UNBIND, null);
+ }
+ case "locationTimeZoneEvent": {
+ LocationTimeZoneEvent event = parseLocationTimeZoneEventArgs(shellCommand);
+ return new SimulatedBinderProviderEvent(providerName,
+ INJECTED_EVENT_TYPE_LOCATION_TIME_ZONE_EVENT, event);
+ }
+ default: {
+ throw new IllegalArgumentException("Unknown simulated event type=" + injectedEvent);
+ }
+ }
+ }
+
+ private static LocationTimeZoneEvent parseLocationTimeZoneEventArgs(ShellCommand shellCommand) {
+ LocationTimeZoneEvent.Builder eventBuilder = new LocationTimeZoneEvent.Builder()
+ .setElapsedRealtimeNanos(SystemClock.elapsedRealtime())
+ .setUserHandle(UserHandle.of(ActivityManager.getCurrentUser()));
+
+ String eventTypeString = shellCommand.getNextArgRequired();
+ switch (eventTypeString.toUpperCase()) {
+ case "PERMANENT_FAILURE": {
+ eventBuilder.setEventType(EVENT_TYPE_PERMANENT_FAILURE);
+ break;
+ }
+ case "UNCERTAIN": {
+ eventBuilder.setEventType(EVENT_TYPE_UNCERTAIN);
+ break;
+ }
+ case "SUCCESS": {
+ eventBuilder.setEventType(EVENT_TYPE_SUCCESS)
+ .setTimeZoneIds(parseTimeZoneArgs(shellCommand));
+ break;
+ }
+ default: {
+ throw new IllegalArgumentException("Error: Unknown eventType: " + eventTypeString);
+ }
+ }
+ return eventBuilder.build();
+ }
+
+ private static List<String> parseTimeZoneArgs(ShellCommand shellCommand) {
+ List<String> timeZoneIds = new ArrayList<>();
+ String timeZoneId;
+ while ((timeZoneId = shellCommand.getNextArg()) != null) {
+ timeZoneIds.add(timeZoneId);
+ }
+ return timeZoneIds;
+ }
+
+ @Override
+ public String toString() {
+ return "SimulatedBinderProviderEvent{"
+ + "mProviderName=" + mProviderName
+ + ", mEventType=" + mEventType
+ + ", mLocationTimeZoneEvent=" + mLocationTimeZoneEvent
+ + '}';
+ }
+}
diff --git a/services/core/java/com/android/server/location/timezone/SimulatedLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/location/timezone/SimulatedLocationTimeZoneProviderProxy.java
new file mode 100644
index 000000000000..462bcab80c1b
--- /dev/null
+++ b/services/core/java/com/android/server/location/timezone/SimulatedLocationTimeZoneProviderProxy.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.location.timezone;
+
+import static com.android.server.location.timezone.SimulatedBinderProviderEvent.INJECTED_EVENT_TYPE_LOCATION_TIME_ZONE_EVENT;
+import static com.android.server.location.timezone.SimulatedBinderProviderEvent.INJECTED_EVENT_TYPE_ON_BIND;
+import static com.android.server.location.timezone.SimulatedBinderProviderEvent.INJECTED_EVENT_TYPE_ON_UNBIND;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.location.timezone.LocationTimeZoneProviderRequest;
+import com.android.server.timezonedetector.ReferenceWithHistory;
+
+import java.util.Objects;
+
+/**
+ * A replacement for a real binder proxy for use during integration testing
+ * that can be used to inject simulated {@link LocationTimeZoneProviderProxy} behavior.
+ */
+class SimulatedLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy {
+
+ @GuardedBy("mProxyLock")
+ @NonNull private LocationTimeZoneProviderRequest mRequest;
+
+ @NonNull private ReferenceWithHistory<String> mLastEvent = new ReferenceWithHistory<>(50);
+
+ SimulatedLocationTimeZoneProviderProxy(
+ @NonNull Context context, @NonNull ThreadingDomain threadingDomain) {
+ super(context, threadingDomain);
+ mRequest = LocationTimeZoneProviderRequest.EMPTY_REQUEST;
+ }
+
+ void simulate(@NonNull SimulatedBinderProviderEvent event) {
+ switch (event.getEventType()) {
+ case INJECTED_EVENT_TYPE_ON_BIND: {
+ mLastEvent.set("Simulating onProviderBound(), event=" + event);
+ mThreadingDomain.post(this::onBindOnHandlerThread);
+ break;
+ }
+ case INJECTED_EVENT_TYPE_ON_UNBIND: {
+ mLastEvent.set("Simulating onProviderUnbound(), event=" + event);
+ mThreadingDomain.post(this::onUnbindOnHandlerThread);
+ break;
+ }
+ case INJECTED_EVENT_TYPE_LOCATION_TIME_ZONE_EVENT: {
+ if (!mRequest.getReportLocationTimeZone()) {
+ mLastEvent.set("Test event=" + event + " is testing an invalid case:"
+ + " reporting is off. mRequest=" + mRequest);
+ }
+ mLastEvent.set("Simulating LocationTimeZoneEvent, event=" + event);
+ handleLocationTimeZoneEvent(event.getLocationTimeZoneEvent());
+ break;
+ }
+ default: {
+ mLastEvent.set("Unknown simulated event type. event=" + event);
+ throw new IllegalArgumentException("Unknown simulated event type. event=" + event);
+ }
+ }
+ }
+
+ private void onBindOnHandlerThread() {
+ mThreadingDomain.assertCurrentThread();
+
+ synchronized (mSharedLock) {
+ mListener.onProviderBound();
+ }
+ }
+
+ private void onUnbindOnHandlerThread() {
+ mThreadingDomain.assertCurrentThread();
+
+ synchronized (mSharedLock) {
+ mListener.onProviderUnbound();
+ }
+ }
+
+ @Override
+ final void setRequest(@NonNull LocationTimeZoneProviderRequest request) {
+ mThreadingDomain.assertCurrentThread();
+
+ Objects.requireNonNull(request);
+ synchronized (mSharedLock) {
+ mLastEvent.set("Request received: " + request);
+ mRequest = request;
+ }
+ }
+
+ @Override
+ public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
+ synchronized (mSharedLock) {
+ ipw.println("mRequest=" + mRequest);
+ ipw.println("mLastEvent=" + mLastEvent);
+
+ ipw.increaseIndent();
+ ipw.println("Last event history:");
+ mLastEvent.dump(ipw);
+ ipw.decreaseIndent();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/location/timezone/ThreadingDomain.java b/services/core/java/com/android/server/location/timezone/ThreadingDomain.java
new file mode 100644
index 000000000000..9b9c82358974
--- /dev/null
+++ b/services/core/java/com/android/server/location/timezone/ThreadingDomain.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 com.android.server.location.timezone;
+
+import android.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * A class that can be used to enforce / indicate a set of components that need to share threading
+ * behavior such as a shared lock object and a common thread, with async execution support.
+ *
+ * <p>It is <em>not</em> essential that the object returned by {@link #getLockObject()} is only used
+ * when executing on the domain's thread, but users should be careful to avoid deadlocks when
+ * multiple locks / threads are in use. Generally sticking to a single thread / lock is safest.
+ */
+abstract class ThreadingDomain {
+
+ @NonNull private final Object mLockObject;
+
+ ThreadingDomain() {
+ mLockObject = new Object();
+ }
+
+ /**
+ * Returns the common lock object for this threading domain that can be used for synchronized ()
+ * blocks. The lock is unique to this threading domain.
+ */
+ @NonNull
+ Object getLockObject() {
+ return mLockObject;
+ }
+
+ /**
+ * Returns the Thread associated with this threading domain.
+ */
+ @NonNull
+ abstract Thread getThread();
+
+ /**
+ * Asserts the currently executing thread is the one associated with this threading domain.
+ * Generally useful for documenting expectations in the code. By asserting a single thread is
+ * being used within a set of components, a lot of races can be avoided.
+ */
+ void assertCurrentThread() {
+ Preconditions.checkArgument(Thread.currentThread() == getThread());
+ }
+
+ /**
+ * Execute the supplied runnable on the threading domain's thread.
+ */
+ abstract void post(@NonNull Runnable runnable);
+
+ /**
+ * Execute the supplied runnable on the threading domain's thread with a delay.
+ */
+ abstract void postDelayed(@NonNull Runnable runnable, long delayMillis);
+
+ abstract void postDelayed(Runnable r, Object token, long delayMillis);
+
+ abstract void removeQueuedRunnables(Object token);
+
+ /**
+ * Creates a new {@link SingleRunnableQueue} that can be used to ensure that (at most) a
+ * single runnable for a given purpose is ever queued. Create new ones for different purposes.
+ */
+ SingleRunnableQueue createSingleRunnableQueue() {
+ return new SingleRunnableQueue();
+ }
+
+ /**
+ * 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
+ * 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();
+ }
+
+ /**
+ * Posts the supplied {@link Runnable} asynchronously and delayed on the threading domain
+ * handler 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 runDelayed(Runnable r, long delayMillis) {
+ cancel();
+ ThreadingDomain.this.postDelayed(r, this, delayMillis);
+ }
+
+ /**
+ * Cancels any queued but not-yet-executed {@link Runnable} previously added by this.
+ */
+ public void cancel() {
+ assertCurrentThread();
+ removeQueuedRunnables(this);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 1b43db2a421d..68961d9c94d8 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;
@@ -2609,6 +2610,10 @@ 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);
@@ -2669,7 +2674,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,
@@ -3501,6 +3506,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/pm/ApkChecksums.java b/services/core/java/com/android/server/pm/ApkChecksums.java
index c6c80aef4432..8640dbcab33c 100644
--- a/services/core/java/com/android/server/pm/ApkChecksums.java
+++ b/services/core/java/com/android/server/pm/ApkChecksums.java
@@ -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();
@@ -190,6 +188,14 @@ public class ApkChecksums {
dos.writeInt(valueBytes.length);
dos.write(valueBytes);
+ final String packageName = checksum.getSourcePackageName();
+ if (packageName == null) {
+ dos.writeInt(-1);
+ } else {
+ dos.writeInt(packageName.length());
+ dos.writeUTF(packageName);
+ }
+
final Certificate cert = checksum.getSourceCertificate();
final byte[] certBytes = (cert == null) ? null : cert.getEncoded();
if (certBytes == null) {
@@ -218,9 +224,19 @@ public class ApkChecksums {
} else {
splitName = dis.readUTF();
}
+
final int kind = 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) {
@@ -230,7 +246,7 @@ public class ApkChecksums {
dis.read(certBytes);
}
checksums[i] = new ApkChecksum(splitName, new Checksum(kind, valueBytes),
- certBytes);
+ packageName, certBytes);
}
return checksums;
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 8468f23b0a6b..2a189c0ebb0c 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -52,6 +52,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;
@@ -153,6 +154,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;
@@ -240,6 +242,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";
@@ -394,10 +397,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;
}
@@ -405,6 +411,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
return mChecksum;
}
+ String getPackageName() {
+ return mPackageName;
+ }
+
byte[] getCertificate() {
return mCertificate;
}
@@ -745,7 +755,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
info.mode = params.mode;
info.installReason = params.installReason;
info.sizeBytes = params.sizeBytes;
- info.appPackageName = params.appPackageName;
+ info.appPackageName = mPackageName != null ? mPackageName : params.appPackageName;
if (includeIcon) {
info.appIcon = params.appIcon;
}
@@ -951,23 +961,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();
@@ -979,7 +992,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));
}
}
}
@@ -2357,6 +2371,17 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
"Invalid filename: " + targetName);
}
+ // Yell loudly if installers drop attribute installLocation when apps explicitly set.
+ if (apk.installLocation != PackageInfo.INSTALL_LOCATION_UNSPECIFIED) {
+ final String installerPackageName = getInstallerPackageName();
+ if (installerPackageName != null
+ && (params.installLocation != apk.installLocation)) {
+ Slog.wtf(TAG, installerPackageName
+ + " drops manifest attribute android:installLocation in " + targetName
+ + " for " + mPackageName);
+ }
+ }
+
final File targetFile = new File(stageDir, targetName);
resolveAndStageFileLocked(addedFile, targetFile, apk.splitName);
@@ -2622,7 +2647,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;
}
@@ -2640,11 +2665,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);
@@ -3964,6 +3995,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
writeIntAttribute(out, ATTR_CHECKSUM_KIND, checksum.getChecksum().getKind());
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);
@@ -4116,6 +4148,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 0f455fdf764b..697c31a78ec7 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1736,15 +1736,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;
@@ -1850,7 +1849,7 @@ public class PackageManagerService extends IPackageManager.Stub
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
synchronized (mLock) {
removeMessages(WRITE_PACKAGE_LIST);
- mPermissionManager.writePermissionsStateToPackageSettingsTEMP();
+ mPermissionManager.writeStateToPackageSettingsTEMP();
mSettings.writePackageListLPr(msg.arg1);
}
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
@@ -3235,17 +3234,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
@@ -3282,7 +3282,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");
@@ -3524,7 +3524,7 @@ public class PackageManagerService extends IPackageManager.Stub
+ ((SystemClock.uptimeMillis()-startTime)/1000f)
+ " seconds");
- mPermissionManager.readPermissionsStateFromPackageSettingsTEMP();
+ mPermissionManager.readStateFromPackageSettingsTEMP();
// If the platform SDK has changed since the last time we booted,
// we need to re-grant app permission to catch any new ones that
// appear. This is really a hack, and means that apps can in some
@@ -5427,6 +5427,23 @@ public class PackageManagerService extends IPackageManager.Stub
}
@Override
+ public int getTargetSdkVersion(String packageName) {
+ synchronized (mLock) {
+ final AndroidPackage pkg = mPackages.get(packageName);
+ if (pkg == null) {
+ return -1;
+ }
+
+ final PackageSetting ps = getPackageSetting(pkg.getPackageName());
+ if (shouldFilterApplicationLocked(ps, Binder.getCallingUid(),
+ UserHandle.getCallingUserId())) {
+ return -1;
+ }
+ return pkg.getTargetSdkVersion();
+ }
+ }
+
+ @Override
public ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) {
return getActivityInfoInternal(component, flags, Binder.getCallingUid(), userId);
}
@@ -6332,10 +6349,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;
}
@@ -6491,9 +6507,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;
}
}
@@ -8992,10 +9009,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();
@@ -9153,9 +9170,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();
@@ -10486,7 +10503,6 @@ public class PackageManagerService extends IPackageManager.Stub
private void clearAppDataLIF(AndroidPackage pkg, int userId, int flags) {
if (pkg == null) {
- Slog.wtf(TAG, "Package was null!", new Throwable());
return;
}
clearAppDataLeafLIF(pkg, userId, flags);
@@ -19806,9 +19822,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;
@@ -21838,7 +21854,7 @@ public class PackageManagerService extends IPackageManager.Stub
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
- mPermissionManager.writePermissionsStateToPackageSettingsTEMP();
+ mPermissionManager.writeStateToPackageSettingsTEMP();
DumpState dumpState = new DumpState();
boolean fullPreferred = false;
@@ -22104,9 +22120,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) {
@@ -23718,7 +23734,7 @@ public class PackageManagerService extends IPackageManager.Stub
mDirtyUsers.remove(userId);
mUserNeedsBadging.delete(userId);
mPermissionManager.onUserRemoved(userId);
- mPermissionManager.writePermissionsStateToPackageSettingsTEMP();
+ mPermissionManager.writeStateToPackageSettingsTEMP();
mSettings.removeUserLPw(userId);
mPendingBroadcasts.remove(userId);
mInstantAppRegistry.onUserRemovedLPw(userId);
@@ -23735,9 +23751,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;
}
@@ -23819,9 +23835,9 @@ public class PackageManagerService extends IPackageManager.Stub
boolean readPermissionStateForUser(@UserIdInt int userId) {
synchronized (mPackages) {
- mPermissionManager.writePermissionsStateToPackageSettingsTEMP();
+ mPermissionManager.writeStateToPackageSettingsTEMP();
mSettings.readPermissionStateForUserSyncLPr(userId);
- mPermissionManager.readPermissionsStateFromPackageSettingsTEMP();
+ mPermissionManager.readStateFromPackageSettingsTEMP();
return mPmInternal.isPermissionUpgradeNeeded(userId);
}
}
@@ -24210,15 +24226,13 @@ public class PackageManagerService extends IPackageManager.Stub
}
@Override
- public int getTargetSdkVersionForPackage(String packageName)
- throws RemoteException {
- int callingUser = UserHandle.getUserId(Binder.getCallingUid());
- ApplicationInfo info = getApplicationInfo(packageName, 0, callingUser);
- if (info == null) {
- throw new RemoteException(
- "Couldn't get ApplicationInfo for package " + packageName);
+ public int getTargetSdkVersionForPackage(String packageName) throws RemoteException {
+ int targetSdk = getTargetSdkVersion(packageName);
+ if (targetSdk != -1) {
+ return targetSdk;
}
- return info.targetSdkVersion;
+
+ throw new RemoteException("Couldn't get targetSdkVersion for package " + packageName);
}
@Override
@@ -25442,11 +25456,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;
}
@@ -25885,12 +25899,12 @@ public class PackageManagerService extends IPackageManager.Stub
/**
* Temporary method that wraps mSettings.writeLPr() and calls
- * mPermissionManager.writePermissionsStateToPackageSettingsTEMP() beforehand.
+ * mPermissionManager.writeStateToPackageSettingsTEMP() beforehand.
*
* TODO(zhanghai): This should be removed once we finish migration of permission storage.
*/
private void writeSettingsLPrTEMP() {
- mPermissionManager.writePermissionsStateToPackageSettingsTEMP();
+ mPermissionManager.writeStateToPackageSettingsTEMP();
mSettings.writeLPr();
}
}
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/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index b12a8f89979b..d4132136c226 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -164,17 +164,6 @@ public class StagingManager {
}
}
- private void updateStoredSession(@NonNull PackageInstallerSession sessionInfo) {
- synchronized (mStagedSessions) {
- PackageInstallerSession storedSession = mStagedSessions.get(sessionInfo.sessionId);
- // storedSession might be null if a call to abortSession was made before the session
- // is updated.
- if (storedSession != null) {
- mStagedSessions.put(sessionInfo.sessionId, sessionInfo);
- }
- }
- }
-
private void markBootCompleted() {
mApexManager.markBootCompleted();
}
@@ -865,7 +854,6 @@ public class StagingManager {
}
void commitSession(@NonNull PackageInstallerSession session) {
- updateStoredSession(session);
mPreRebootVerificationHandler.startPreRebootVerification(session);
}
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 e848bb7092da..4c58b06e79c6 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;
@@ -765,8 +767,6 @@ public class UserManagerService extends IUserManager.Stub {
return null;
}
- // TODO(b/157921703): replace by getAliveUsers() or remove (so callers
- // explicitly call the 3-booleans version)
public @NonNull List<UserInfo> getUsers(boolean excludeDying) {
return getUsers(/*excludePartial= */ true, excludeDying, /* excludePreCreated= */
true);
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 962638b4f63c..865b8a1e97eb 100644
--- a/services/core/java/com/android/server/pm/permission/BasePermission.java
+++ b/services/core/java/com/android/server/pm/permission/BasePermission.java
@@ -36,6 +36,7 @@ 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;
@@ -139,6 +140,10 @@ public final class BasePermission {
this.perm = perm;
}
+ public boolean hasGids() {
+ return !ArrayUtils.isEmpty(gids);
+ }
+
public int[] computeGids(int userId) {
if (perUser) {
final int[] userGids = new int[gids.length];
@@ -419,9 +424,9 @@ public final class BasePermission {
}
public void enforceDeclaredUsedAndRuntimeOrDevelopment(AndroidPackage pkg,
- PermissionsState permsState) {
+ UidPermissionState uidState) {
int index = pkg.getRequestedPermissions().indexOf(name);
- if (!permsState.hasRequestedPermission(name) && index == -1) {
+ if (!uidState.hasRequestedPermission(name) && index == -1) {
throw new SecurityException("Package " + pkg.getPackageName()
+ " has not requested permission " + name);
}
diff --git a/services/core/java/com/android/server/pm/permission/DevicePermissionState.java b/services/core/java/com/android/server/pm/permission/DevicePermissionState.java
new file mode 100644
index 000000000000..b9456acfced5
--- /dev/null
+++ b/services/core/java/com/android/server/pm/permission/DevicePermissionState.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.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);
+ }
+ }
+
+ @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;
+ }
+ }
+
+ public void removeUserState(@UserIdInt int userId) {
+ synchronized (mLock) {
+ 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;
+ }
+ }
+}
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 ccf850fea7fd..aa327ba02356 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -53,7 +53,7 @@ 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.PermissionsState.PERMISSION_OPERATION_FAILURE;
+import static com.android.server.pm.permission.UidPermissionState.PERMISSION_OPERATION_FAILURE;
import static java.util.concurrent.TimeUnit.SECONDS;
@@ -137,6 +137,7 @@ 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;
@@ -148,12 +149,9 @@ import com.android.server.pm.permission.PermissionManagerServiceInternal.Default
import com.android.server.pm.permission.PermissionManagerServiceInternal.DefaultDialerProvider;
import com.android.server.pm.permission.PermissionManagerServiceInternal.DefaultHomeProvider;
import com.android.server.pm.permission.PermissionManagerServiceInternal.PermissionCallback;
-import com.android.server.pm.permission.PermissionsState.PermissionState;
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;
@@ -226,8 +224,8 @@ public class PermissionManagerService extends IPermissionManager.Stub {
/** Internal connection to the user manager */
private final UserManagerInternal mUserManagerInt;
- /** Maps from App ID to PermissionsState */
- private final SparseArray<PermissionsState> mAppIdStates = new SparseArray<>();
+ @NonNull
+ private final DevicePermissionState mState;
/** Permission controller: User space permission management */
private PermissionControllerManager mPermissionControllerManager;
@@ -395,6 +393,7 @@ 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,
@@ -681,12 +680,12 @@ public class PermissionManagerService extends IPermissionManager.Stub {
if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
return 0;
}
- final PermissionsState permissionsState = getPermissionsState(pkg);
- if (permissionsState == null) {
- Slog.e(TAG, "Missing permissions state for " + packageName);
+ final UidPermissionState uidState = getUidState(pkg, userId);
+ if (uidState == null) {
+ Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId);
return 0;
}
- return permissionsState.getPermissionFlags(permName, userId);
+ return uidState.getPermissionFlags(permName);
}
@Override
@@ -788,14 +787,13 @@ public class PermissionManagerService extends IPermissionManager.Stub {
throw new IllegalArgumentException("Unknown permission: " + permName);
}
- final PermissionsState permissionsState = getPermissionsState(pkg);
- if (permissionsState == null) {
- Slog.e(TAG, "Missing permissions state for " + packageName);
+ final UidPermissionState uidState = getUidState(pkg, userId);
+ if (uidState == null) {
+ Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId);
return;
}
- final boolean hadState =
- permissionsState.getRuntimePermissionState(permName, userId) != null;
+ final boolean hadState = uidState.getPermissionState(permName) != null;
if (!hadState) {
boolean isRequested = false;
// Fast path, the current package has requested the permission.
@@ -822,20 +820,18 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
}
final boolean permissionUpdated =
- permissionsState.updatePermissionFlags(bp, userId, flagMask, flagValues);
+ uidState.updatePermissionFlags(bp, flagMask, flagValues);
if (permissionUpdated && bp.isRuntime()) {
notifyRuntimePermissionStateChanged(packageName, userId);
}
if (permissionUpdated && callback != null) {
// Install and runtime permissions are stored in different places,
// so figure out what permission changed and persist the change.
- if (permissionsState.getInstallPermissionState(permName) != null) {
+ if (!bp.isRuntime()) {
int userUid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.getUid()));
callback.onInstallPermissionUpdatedNotifyListener(userUid);
- } else if (permissionsState.getRuntimePermissionState(permName, userId) != null
- || hadState) {
- callback.onPermissionUpdatedNotifyListener(new int[]{userId}, false,
- pkg.getUid());
+ } else {
+ callback.onPermissionUpdatedNotifyListener(new int[]{userId}, false, pkg.getUid());
}
}
}
@@ -868,13 +864,14 @@ public class PermissionManagerService extends IPermissionManager.Stub {
final boolean[] changed = new boolean[1];
mPackageManagerInt.forEachPackage(pkg -> {
- final PermissionsState permissionsState = getPermissionsState(pkg);
- if (permissionsState == null) {
- Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName());
+ final UidPermissionState uidState = getUidState(pkg, userId);
+ if (uidState == null) {
+ Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
+ + userId);
return;
}
- changed[0] |= permissionsState.updatePermissionFlagsForAllPermissions(
- userId, effectiveFlagMask, effectiveFlagValues);
+ changed[0] |= uidState.updatePermissionFlagsForAllPermissions(
+ effectiveFlagMask, effectiveFlagValues);
mOnPermissionChangeListeners.onPermissionsChanged(pkg.getUid());
});
@@ -905,6 +902,16 @@ 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;
@@ -926,19 +933,20 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
final int uid = UserHandle.getUid(userId, pkg.getUid());
- final PermissionsState permissionsState = getPermissionsState(pkg);
- if (permissionsState == null) {
- Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName());
+ 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;
}
- if (checkSinglePermissionInternal(uid, permissionsState, permissionName)) {
+ if (checkSinglePermissionInternal(uid, uidState, permissionName)) {
return PackageManager.PERMISSION_GRANTED;
}
final String fullerPermissionName = FULLER_PERMISSION_MAP.get(permissionName);
if (fullerPermissionName != null
- && checkSinglePermissionInternal(uid, permissionsState, fullerPermissionName)) {
+ && checkSinglePermissionInternal(uid, uidState, fullerPermissionName)) {
return PackageManager.PERMISSION_GRANTED;
}
@@ -946,8 +954,8 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
private boolean checkSinglePermissionInternal(int uid,
- @NonNull PermissionsState permissionsState, @NonNull String permissionName) {
- if (!permissionsState.hasPermission(permissionName, UserHandle.getUserId(uid))) {
+ @NonNull UidPermissionState uidState, @NonNull String permissionName) {
+ if (!uidState.hasPermission(permissionName)) {
return false;
}
@@ -981,6 +989,16 @@ 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);
}
@@ -1141,9 +1159,9 @@ public class PermissionManagerService extends IPermissionManager.Stub {
final long identity = Binder.clearCallingIdentity();
try {
- final PermissionsState permissionsState = getPermissionsState(pkg);
- if (permissionsState == null) {
- Slog.e(TAG, "Missing permissions state for " + packageName);
+ final UidPermissionState uidState = getUidState(pkg, userId);
+ if (uidState == null) {
+ Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId);
return null;
}
@@ -1164,7 +1182,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {
for (int i = 0; i < permissionCount; i++) {
final String permissionName = pkg.getRequestedPermissions().get(i);
final int currentFlags =
- permissionsState.getPermissionFlags(permissionName, userId);
+ uidState.getPermissionFlags(permissionName);
if ((currentFlags & queryFlags) != 0) {
if (whitelistedPermissions == null) {
whitelistedPermissions = new ArrayList<>();
@@ -1453,13 +1471,14 @@ public class PermissionManagerService extends IPermissionManager.Stub {
throw new IllegalArgumentException("Unknown package: " + packageName);
}
- final PermissionsState permissionsState = getPermissionsState(pkg);
- if (permissionsState == null) {
- Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName());
+ final UidPermissionState uidState = getUidState(pkg, userId);
+ if (uidState == null) {
+ Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
+ + userId);
return;
}
- bp.enforceDeclaredUsedAndRuntimeOrDevelopment(pkg, permissionsState);
+ 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
@@ -1472,7 +1491,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {
final int uid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.getUid()));
- final int flags = permissionsState.getPermissionFlags(permName, userId);
+ 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);
@@ -1502,8 +1521,9 @@ public class PermissionManagerService extends IPermissionManager.Stub {
if (bp.isDevelopment()) {
// Development permissions must be handled specially, since they are not
// normal runtime permissions. For now they apply to all users.
- if (permissionsState.grantInstallPermission(bp)
- != PERMISSION_OPERATION_FAILURE) {
+ // 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();
}
@@ -1521,13 +1541,13 @@ public class PermissionManagerService extends IPermissionManager.Stub {
return;
}
- final int result = permissionsState.grantRuntimePermission(bp, userId);
+ final int result = uidState.grantPermission(bp);
switch (result) {
case PERMISSION_OPERATION_FAILURE: {
return;
}
- case PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: {
+ case UidPermissionState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: {
if (callback != null) {
callback.onGidsChanged(UserHandle.getAppId(pkg.getUid()), userId);
}
@@ -1617,13 +1637,14 @@ public class PermissionManagerService extends IPermissionManager.Stub {
throw new IllegalArgumentException("Unknown permission: " + permName);
}
- final PermissionsState permissionsState = getPermissionsState(pkg);
- if (permissionsState == null) {
- Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName());
+ final UidPermissionState uidState = getUidState(pkg, userId);
+ if (uidState == null) {
+ Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
+ + userId);
return;
}
- bp.enforceDeclaredUsedAndRuntimeOrDevelopment(pkg, permissionsState);
+ 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
@@ -1634,7 +1655,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {
return;
}
- final int flags = permissionsState.getPermissionFlags(permName, userId);
+ 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) {
@@ -1649,8 +1670,9 @@ public class PermissionManagerService extends IPermissionManager.Stub {
if (bp.isDevelopment()) {
// Development permissions must be handled specially, since they are not
// normal runtime permissions. For now they apply to all users.
- if (permissionsState.revokeInstallPermission(bp)
- != PERMISSION_OPERATION_FAILURE) {
+ // 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();
}
@@ -1659,12 +1681,11 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
// Permission is already revoked, no need to do anything.
- if (!permissionsState.hasRuntimePermission(permName, userId)) {
+ if (!uidState.hasPermission(permName)) {
return;
}
- if (permissionsState.revokeRuntimePermission(bp, userId)
- == PERMISSION_OPERATION_FAILURE) {
+ if (uidState.revokePermission(bp) == PERMISSION_OPERATION_FAILURE) {
return;
}
@@ -2466,19 +2487,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {
private void onUserRemoved(@UserIdInt int userId) {
synchronized (mLock) {
- final int appIdStatesSize = mAppIdStates.size();
- for (int i = 0; i < appIdStatesSize; i++) {
- PermissionsState permissionsState = mAppIdStates.valueAt(i);
- for (PermissionState permissionState
- : permissionsState.getRuntimePermissionStates(userId)) {
- BasePermission bp = mSettings.getPermission(permissionState.getName());
- if (bp != null) {
- permissionsState.revokeRuntimePermission(bp, userId);
- permissionsState.updatePermissionFlags(bp, userId,
- PackageManager.MASK_PERMISSION_FLAGS_ALL, 0);
- }
- }
- }
+ mState.removeUserState(userId);
}
}
@@ -2489,19 +2498,18 @@ public class PermissionManagerService extends IPermissionManager.Stub {
if (ps == null) {
return Collections.emptySet();
}
- final PermissionsState permissionsState = getPermissionsState(ps);
- if (permissionsState == null) {
- Slog.e(TAG, "Missing permissions state for " + packageName);
+ 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 permissionsState.getPermissions(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<>(permissionsState.getPermissions(
- userId));
+ final Set<String> instantPermissions = new ArraySet<>(uidState.getPermissions());
instantPermissions.removeIf(permissionName -> {
BasePermission permission = mSettings.getPermission(permissionName);
if (permission == null) {
@@ -2533,12 +2541,12 @@ public class PermissionManagerService extends IPermissionManager.Stub {
if (ps == null) {
return null;
}
- final PermissionsState permissionsState = getPermissionsState(ps);
- if (permissionsState == null) {
- Slog.e(TAG, "Missing permissions state for " + packageName);
+ final UidPermissionState uidState = getUidState(ps, userId);
+ if (uidState == null) {
+ Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId);
return null;
}
- return permissionsState.computeGids(userId);
+ return uidState.computeGids(userId);
}
/**
@@ -2575,15 +2583,17 @@ public class PermissionManagerService extends IPermissionManager.Stub {
if (ps == null) {
return;
}
- final PermissionsState permissionsState = getOrCreatePermissionsState(ps);
final int[] userIds = getAllUserIds();
boolean runtimePermissionsRevoked = false;
int[] updatedUserIds = EMPTY_INT_ARRAY;
- for (int userId : userIds) {
- if (permissionsState.isMissing(userId)) {
+ for (final int userId : userIds) {
+ final UserPermissionState userState = mState.getOrCreateUserState(userId);
+ final UidPermissionState uidState = userState.getOrCreateUidState(ps.getAppId());
+
+ if (uidState.isMissing()) {
Collection<String> requestedPermissions;
int targetSdkVersion;
if (!ps.isSharedUser()) {
@@ -2611,222 +2621,221 @@ public class PermissionManagerService extends IPermissionManager.Stub {
&& permission.isRuntime() && !permission.isRemoved()) {
if (permission.isHardOrSoftRestricted()
|| permission.isImmutablyRestricted()) {
- permissionsState.updatePermissionFlags(permission, userId,
+ uidState.updatePermissionFlags(permission,
FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT,
FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT);
}
if (targetSdkVersion < Build.VERSION_CODES.M) {
- permissionsState.updatePermissionFlags(permission, userId,
+ uidState.updatePermissionFlags(permission,
PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
| PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
| PackageManager.FLAG_PERMISSION_REVOKED_COMPAT);
- permissionsState.grantRuntimePermission(permission, userId);
+ uidState.grantPermission(permission);
}
}
}
- permissionsState.setMissing(false, userId);
+ uidState.setMissing(false);
updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
}
- }
- PermissionsState origPermissions = permissionsState;
+ UidPermissionState origState = uidState;
- boolean changedInstallPermission = false;
+ boolean changedInstallPermission = false;
- if (replace) {
- ps.setInstallPermissionsFixed(false);
- if (!ps.isSharedUser()) {
- origPermissions = new PermissionsState(permissionsState);
- permissionsState.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) {
- updatedUserIds = revokeUnusedSharedUserPermissionsLocked(
- ps.getSharedUser().getPackages(), permissionsState, userIds);
- if (!ArrayUtils.isEmpty(updatedUserIds)) {
- 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;
+ }
}
}
}
- }
-
- permissionsState.setGlobalGids(mGlobalGids);
- ArraySet<String> newImplicitPermissions = new ArraySet<>();
+ uidState.setGlobalGids(mGlobalGids);
- final int N = pkg.getRequestedPermissions().size();
- for (int i = 0; i < N; i++) {
- final String permName = pkg.getRequestedPermissions().get(i);
+ ArraySet<String> newImplicitPermissions = new ArraySet<>();
final String friendlyName = pkg.getPackageName() + "(" + pkg.getUid() + ")";
- final BasePermission bp = mSettings.getPermission(permName);
- final boolean appSupportsRuntimePermissions =
- pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M;
- String upgradedActivityRecognitionPermission = null;
- if (DEBUG_INSTALL && bp != null) {
- Log.i(TAG, "Package " + friendlyName
- + " checking " + permName + ": " + bp);
- }
+ 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;
+
+ 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);
+ 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 (!origPermissions.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.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);
- 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)
- && origPermissions.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.
+ 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);
+ 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 " + friendlyName);
-// }
-// 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;
-
- // Keep track of app op permissions.
- if (bp.isAppOp()) {
- mSettings.addAppOpPackage(perm, pkg.getPackageName());
- }
+ final String perm = bp.getName();
+ boolean allowedSig = false;
+ int grant = GRANT_DENIED;
- if (bp.isNormal()) {
- // For all apps normal permissions are install time ones.
- grant = GRANT_INSTALL;
- } else if (bp.isRuntime()) {
- if (origPermissions.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, origPermissions);
- if (allowedSig) {
+
+ 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;
+ }
+ } 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() && ps.areInstallPermissionsFixed() && !bp.isRuntime()) {
- // 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 && !origPermissions.hasInstallPermission(perm)) {
- // 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;
+ if (grant != GRANT_DENIED) {
+ if (!ps.isSystem() && userState.areInstallPermissionsFixed(ps.name)
+ && !bp.isRuntime()) {
+ // 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)) {
+ // 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;
+ }
}
}
}
- }
- if (DEBUG_PERMISSIONS) {
- Slog.i(TAG, "Considering granting permission " + perm + " to package "
- + pkg.getPackageName());
- }
+ 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
- for (int userId : userIds) {
- if (origPermissions.getRuntimePermissionState(
- perm, userId) != null) {
+ 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.
- origPermissions.revokeRuntimePermission(bp, userId);
- origPermissions.updatePermissionFlags(bp, userId,
+ 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 (permissionsState.grantInstallPermission(bp) !=
- PERMISSION_OPERATION_FAILURE) {
- changedInstallPermission = true;
- }
- } break;
+ // 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();
+ case GRANT_RUNTIME: {
+ boolean hardRestricted = bp.isHardRestricted();
+ boolean softRestricted = bp.isSoftRestricted();
- for (int userId : userIds) {
// 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.
@@ -2834,25 +2843,24 @@ public class PermissionManagerService extends IPermissionManager.Stub {
mPermissionPolicyInternal != null
&& mPermissionPolicyInternal.isInitialized(userId);
- PermissionState permState = origPermissions
- .getRuntimePermissionState(perm, userId);
- int flags = permState != null ? permState.getFlags() : 0;
+ PermissionState origPermState = origState.getPermissionState(perm);
+ int flags = origPermState != null ? origPermState.getFlags() : 0;
boolean wasChanged = false;
boolean restrictionExempt =
- (origPermissions.getPermissionFlags(bp.name, userId)
+ (origState.getPermissionFlags(bp.name)
& FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0;
- boolean restrictionApplied = (origPermissions.getPermissionFlags(
- bp.name, userId) & FLAG_PERMISSION_APPLY_RESTRICTION) != 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 (permState != null && permState.isGranted()
- && permissionsState.revokeRuntimePermission(
- bp, userId) != PERMISSION_OPERATION_FAILURE) {
+ if (origPermState != null && origPermState.isGranted()
+ && uidState.revokePermission(
+ bp) != PERMISSION_OPERATION_FAILURE) {
wasChanged = true;
}
if (!restrictionApplied) {
@@ -2882,15 +2890,15 @@ public class PermissionManagerService extends IPermissionManager.Stub {
// Hard restricted permissions cannot be held.
} else if (!permissionPolicyInitialized
|| (!hardRestricted || restrictionExempt)) {
- if (permState != null && permState.isGranted()) {
- if (permissionsState.grantRuntimePermission(bp, userId)
+ if (origPermState != null && origPermState.isGranted()) {
+ if (uidState.grantPermission(bp)
== PERMISSION_OPERATION_FAILURE) {
wasChanged = true;
}
}
}
} else {
- if (permState == null) {
+ if (origPermState == null) {
// New permission
if (PLATFORM_PACKAGE_NAME.equals(
bp.getSourcePackageName())) {
@@ -2902,8 +2910,8 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
}
- if (!permissionsState.hasRuntimePermission(bp.name, userId)
- && permissionsState.grantRuntimePermission(bp, userId)
+ if (!uidState.hasPermission(bp.name)
+ && uidState.grantPermission(bp)
!= PERMISSION_OPERATION_FAILURE) {
wasChanged = true;
}
@@ -2936,36 +2944,32 @@ public class PermissionManagerService extends IPermissionManager.Stub {
updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
}
- permissionsState.updatePermissionFlags(bp, userId,
- MASK_PERMISSION_FLAGS_ALL, flags);
- }
- } break;
-
- case GRANT_UPGRADE: {
- // Upgrade from Pre-Q to Q permission model. Make all permissions
- // runtime
- PermissionState permState = origPermissions
- .getInstallPermissionState(perm);
- int flags = (permState != null) ? permState.getFlags() : 0;
-
- BasePermission bpToRevoke =
- upgradedActivityRecognitionPermission == null
- ? bp : mSettings.getPermissionLocked(
- upgradedActivityRecognitionPermission);
- // Remove install permission
- if (origPermissions.revokeInstallPermission(bpToRevoke)
- != PERMISSION_OPERATION_FAILURE) {
- origPermissions.updatePermissionFlags(bpToRevoke,
- UserHandle.USER_ALL,
- (MASK_PERMISSION_FLAGS_ALL
- & ~FLAG_PERMISSION_APPLY_RESTRICTION), 0);
- changedInstallPermission = 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();
+ boolean hardRestricted = bp.isHardRestricted();
+ boolean softRestricted = bp.isSoftRestricted();
- for (int userId : userIds) {
// 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.
@@ -2976,18 +2980,18 @@ public class PermissionManagerService extends IPermissionManager.Stub {
boolean wasChanged = false;
boolean restrictionExempt =
- (origPermissions.getPermissionFlags(bp.name, userId)
+ (origState.getPermissionFlags(bp.name)
& FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0;
- boolean restrictionApplied = (origPermissions.getPermissionFlags(
- bp.name, userId) & FLAG_PERMISSION_APPLY_RESTRICTION) != 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 (permState != null && permState.isGranted()
- && permissionsState.revokeRuntimePermission(
- bp, userId) != PERMISSION_OPERATION_FAILURE) {
+ if (origPermState != null && origPermState.isGranted()
+ && uidState.revokePermission(
+ bp) != PERMISSION_OPERATION_FAILURE) {
wasChanged = true;
}
if (!restrictionApplied) {
@@ -3017,15 +3021,15 @@ public class PermissionManagerService extends IPermissionManager.Stub {
// Hard restricted permissions cannot be held.
} else if (!permissionPolicyInitialized ||
(!hardRestricted || restrictionExempt)) {
- if (permissionsState.grantRuntimePermission(bp, userId) !=
- PERMISSION_OPERATION_FAILURE) {
+ if (uidState.grantPermission(bp)
+ != PERMISSION_OPERATION_FAILURE) {
wasChanged = true;
}
}
} else {
- if (!permissionsState.hasRuntimePermission(bp.name, userId)
- && permissionsState.grantRuntimePermission(bp,
- userId) != PERMISSION_OPERATION_FAILURE) {
+ if (!uidState.hasPermission(bp.name)
+ && uidState.grantPermission(bp)
+ != PERMISSION_OPERATION_FAILURE) {
flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
wasChanged = true;
}
@@ -3058,72 +3062,75 @@ public class PermissionManagerService extends IPermissionManager.Stub {
updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
}
- permissionsState.updatePermissionFlags(bp, userId,
+ uidState.updatePermissionFlags(bp,
MASK_PERMISSION_FLAGS_ALL, flags);
- }
- } break;
+ } break;
- 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");
+ 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");
+ }
}
- }
- } break;
- }
- } else {
- if (permissionsState.revokeInstallPermission(bp) !=
- PERMISSION_OPERATION_FAILURE) {
- // Also drop the permission flags.
- permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
- MASK_PERMISSION_FLAGS_ALL, 0);
- changedInstallPermission = true;
- if (DEBUG_PERMISSIONS) {
- Slog.i(TAG, "Un-granting permission " + perm
- + " from package " + friendlyName
- + " (protectionLevel=" + bp.getProtectionLevel()
- + " flags=0x"
- + Integer.toHexString(PackageInfoUtils.appInfoFlags(pkg, ps))
- + ")");
+ } break;
}
- } 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))
- + ")");
+ } 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
+ + " from package " + friendlyName
+ + " (protectionLevel=" + bp.getProtectionLevel()
+ + " flags=0x"
+ + Integer.toHexString(PackageInfoUtils.appInfoFlags(pkg,
+ 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 ((changedInstallPermission || replace) && !ps.areInstallPermissionsFixed() &&
- !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.
- ps.setInstallPermissionsFixed(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(permissionsState, pkg,
- userIds, updatedUserIds);
- updatedUserIds = setInitialGrantForNewImplicitPermissionsLocked(origPermissions,
- permissionsState, pkg, newImplicitPermissions, userIds, updatedUserIds);
- updatedUserIds = checkIfLegacyStorageOpsNeedToBeUpdated(pkg, replace, userIds,
- updatedUserIds);
+ synchronized (mLock) {
+ updatedUserIds = revokePermissionsNoLongerImplicitLocked(uidState, pkg,
+ userId, updatedUserIds);
+ updatedUserIds = setInitialGrantForNewImplicitPermissionsLocked(origState,
+ uidState, pkg, newImplicitPermissions, userId, updatedUserIds);
+ }
}
+ updatedUserIds = checkIfLegacyStorageOpsNeedToBeUpdated(pkg, replace, userIds,
+ updatedUserIds);
+
// TODO: Kill UIDs whose GIDs or runtime permissions changed. This might be more important
// for shared users.
// Persist the runtime permissions state for users with changes. If permissions
@@ -3159,40 +3166,38 @@ public class PermissionManagerService extends IPermissionManager.Stub {
*
* @return The updated value of the {@code updatedUserIds} parameter
*/
- private @NonNull int[] revokePermissionsNoLongerImplicitLocked(@NonNull PermissionsState ps,
- @NonNull AndroidPackage pkg, @NonNull int[] userIds, @NonNull int[] updatedUserIds) {
+ private @NonNull int[] revokePermissionsNoLongerImplicitLocked(@NonNull UidPermissionState ps,
+ @NonNull AndroidPackage pkg, int userId, @NonNull int[] updatedUserIds) {
String pkgName = pkg.getPackageName();
boolean supportsRuntimePermissions = pkg.getTargetSdkVersion()
>= Build.VERSION_CODES.M;
- for (int userId : userIds) {
- for (String permission : ps.getPermissions(userId)) {
- if (!pkg.getImplicitPermissions().contains(permission)) {
- if (!ps.hasInstallPermission(permission)) {
- int flags = ps.getRuntimePermissionState(permission, userId).getFlags();
+ for (String permission : ps.getPermissions()) {
+ if (!pkg.getImplicitPermissions().contains(permission)) {
+ if (!ps.hasInstallPermission(permission)) {
+ int flags = ps.getPermissionFlags(permission);
- if ((flags & FLAG_PERMISSION_REVOKE_WHEN_REQUESTED) != 0) {
- BasePermission bp = mSettings.getPermissionLocked(permission);
+ if ((flags & FLAG_PERMISSION_REVOKE_WHEN_REQUESTED) != 0) {
+ BasePermission bp = mSettings.getPermissionLocked(permission);
- int flagsToRemove = FLAG_PERMISSION_REVOKE_WHEN_REQUESTED;
+ int flagsToRemove = FLAG_PERMISSION_REVOKE_WHEN_REQUESTED;
- if ((flags & BLOCKING_PERMISSION_FLAGS) == 0
- && supportsRuntimePermissions) {
- int revokeResult = ps.revokeRuntimePermission(bp, userId);
- if (revokeResult != PERMISSION_OPERATION_FAILURE) {
- if (DEBUG_PERMISSIONS) {
- Slog.i(TAG, "Revoking runtime permission "
- + permission + " for " + pkgName
- + " as it is now requested");
- }
+ if ((flags & BLOCKING_PERMISSION_FLAGS) == 0
+ && supportsRuntimePermissions) {
+ int revokeResult = ps.revokePermission(bp);
+ if (revokeResult != PERMISSION_OPERATION_FAILURE) {
+ if (DEBUG_PERMISSIONS) {
+ Slog.i(TAG, "Revoking runtime permission "
+ + permission + " for " + pkgName
+ + " as it is now requested");
}
-
- flagsToRemove |= USER_PERMISSION_FLAGS;
}
- ps.updatePermissionFlags(bp, userId, flagsToRemove, 0);
- updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
+ flagsToRemove |= USER_PERMISSION_FLAGS;
}
+
+ ps.updatePermissionFlags(bp, flagsToRemove, 0);
+ updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
}
}
}
@@ -3213,12 +3218,10 @@ public class PermissionManagerService extends IPermissionManager.Stub {
* @param newPerm The permission to inherit to
* @param ps The permission state of the package
* @param pkg The package requesting the permissions
- * @param userId The user the permission belongs to
*/
private void inheritPermissionStateToNewImplicitPermissionLocked(
@NonNull ArraySet<String> sourcePerms, @NonNull String newPerm,
- @NonNull PermissionsState ps, @NonNull AndroidPackage pkg,
- @UserIdInt int userId) {
+ @NonNull UidPermissionState ps, @NonNull AndroidPackage pkg) {
String pkgName = pkg.getPackageName();
boolean isGranted = false;
int flags = 0;
@@ -3226,17 +3229,16 @@ public class PermissionManagerService extends IPermissionManager.Stub {
int numSourcePerm = sourcePerms.size();
for (int i = 0; i < numSourcePerm; i++) {
String sourcePerm = sourcePerms.valueAt(i);
- if ((ps.hasRuntimePermission(sourcePerm, userId))
- || ps.hasInstallPermission(sourcePerm)) {
+ if (ps.hasPermission(sourcePerm)) {
if (!isGranted) {
flags = 0;
}
isGranted = true;
- flags |= ps.getPermissionFlags(sourcePerm, userId);
+ flags |= ps.getPermissionFlags(sourcePerm);
} else {
if (!isGranted) {
- flags |= ps.getPermissionFlags(sourcePerm, userId);
+ flags |= ps.getPermissionFlags(sourcePerm);
}
}
}
@@ -3247,11 +3249,11 @@ public class PermissionManagerService extends IPermissionManager.Stub {
+ " for " + pkgName);
}
- ps.grantRuntimePermission(mSettings.getPermissionLocked(newPerm), userId);
+ ps.grantPermission(mSettings.getPermissionLocked(newPerm));
}
// Add permission flags
- ps.updatePermissionFlags(mSettings.getPermission(newPerm), userId, flags, flags);
+ ps.updatePermissionFlags(mSettings.getPermission(newPerm), flags, flags);
}
/**
@@ -3283,15 +3285,15 @@ public class PermissionManagerService extends IPermissionManager.Stub {
* @param origPs The permission state of the package before the split
* @param ps The new permission state
* @param pkg The package the permission belongs to
- * @param userIds All user IDs in the system, must be passed in because this method is locked
+ * @param userId The user ID
* @param updatedUserIds List of users for which the permission state has already been changed
*
* @return List of users for which the permission state has been changed
*/
private @NonNull int[] setInitialGrantForNewImplicitPermissionsLocked(
- @NonNull PermissionsState origPs, @NonNull PermissionsState ps,
+ @NonNull UidPermissionState origPs, @NonNull UidPermissionState ps,
@NonNull AndroidPackage pkg, @NonNull ArraySet<String> newImplicitPermissions,
- @NonNull int[] userIds, @NonNull int[] updatedUserIds) {
+ @UserIdInt int userId, @NonNull int[] updatedUserIds) {
String pkgName = pkg.getPackageName();
ArrayMap<String, ArraySet<String>> newToSplitPerms = new ArrayMap<>();
@@ -3325,35 +3327,33 @@ public class PermissionManagerService extends IPermissionManager.Stub {
if (!ps.hasInstallPermission(newPerm)) {
BasePermission bp = mSettings.getPermissionLocked(newPerm);
- for (int userId : userIds) {
- if (!newPerm.equals(Manifest.permission.ACTIVITY_RECOGNITION)) {
- ps.updatePermissionFlags(bp, userId,
- FLAG_PERMISSION_REVOKE_WHEN_REQUESTED,
- FLAG_PERMISSION_REVOKE_WHEN_REQUESTED);
- }
- updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
+ if (!newPerm.equals(Manifest.permission.ACTIVITY_RECOGNITION)) {
+ ps.updatePermissionFlags(bp,
+ FLAG_PERMISSION_REVOKE_WHEN_REQUESTED,
+ FLAG_PERMISSION_REVOKE_WHEN_REQUESTED);
+ }
+ 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;
- }
+ boolean inheritsFromInstallPerm = false;
+ for (int sourcePermNum = 0; sourcePermNum < sourcePerms.size();
+ sourcePermNum++) {
+ if (ps.hasInstallPermission(sourcePerms.valueAt(sourcePermNum))) {
+ 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");
- }
- } else {
- // Inherit from new install or existing runtime permissions
- inheritPermissionStateToNewImplicitPermissionLocked(sourcePerms,
- newPerm, ps, pkg, userId);
+ 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");
}
+ } else {
+ // Inherit from new install or existing runtime permissions
+ inheritPermissionStateToNewImplicitPermissionLocked(sourcePerms,
+ newPerm, ps, pkg);
}
}
}
@@ -3484,7 +3484,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
private boolean shouldGrantSignaturePermission(String perm, AndroidPackage pkg,
- PackageSetting pkgSetting, BasePermission bp, PermissionsState origPermissions) {
+ PackageSetting pkgSetting, BasePermission bp, UidPermissionState origPermissions) {
boolean oemPermission = bp.isOEM();
boolean vendorPrivilegedPermission = bp.isVendorPrivileged();
boolean privilegedPermission = bp.isPrivileged() || bp.isVendorPrivileged();
@@ -3760,12 +3760,13 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
// Legacy apps have the permission and get user consent on launch.
- final PermissionsState permissionsState = getPermissionsState(pkg);
- if (permissionsState == null) {
- Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName());
+ final UidPermissionState uidState = getUidState(pkg, userId);
+ if (uidState == null) {
+ Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
+ + userId);
return false;
}
- return permissionsState.isPermissionReviewRequired(userId);
+ return uidState.isPermissionReviewRequired();
}
private boolean isPackageRequestingPermission(AndroidPackage pkg, String permission) {
@@ -3789,9 +3790,10 @@ public class PermissionManagerService extends IPermissionManager.Stub {
private void grantRequestedRuntimePermissionsForUser(AndroidPackage pkg, int userId,
String[] grantedPermissions, int callingUid, PermissionCallback callback) {
- final PermissionsState permissionsState = getPermissionsState(pkg);
- if (permissionsState == null) {
- Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName());
+ final UidPermissionState uidState = getUidState(pkg, userId);
+ if (uidState == null) {
+ Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
+ + userId);
return;
}
@@ -3816,7 +3818,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {
&& (supportsRuntimePermissions || !bp.isRuntimeOnly())
&& (grantedPermissions == null
|| ArrayUtils.contains(grantedPermissions, permission))) {
- final int flags = permissionsState.getPermissionFlags(permission, userId);
+ final int flags = uidState.getPermissionFlags(permission);
if (supportsRuntimePermissions) {
// Installer cannot change immutable permissions.
if ((flags & immutableFlags) == 0) {
@@ -3838,18 +3840,19 @@ public class PermissionManagerService extends IPermissionManager.Stub {
private void setWhitelistedRestrictedPermissionsForUsers(@NonNull AndroidPackage pkg,
@UserIdInt int[] userIds, @Nullable List<String> permissions, int callingUid,
@PermissionWhitelistFlags int whitelistFlags, PermissionCallback callback) {
- final PermissionsState permissionsState = getPermissionsState(pkg);
- if (permissionsState == null) {
- Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName());
- return;
- }
-
SparseArray<ArraySet<String>> oldGrantedRestrictedPermissions = new SparseArray<>();
boolean updatePermissions = false;
final int permissionCount = pkg.getRequestedPermissions().size();
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);
@@ -3859,14 +3862,14 @@ public class PermissionManagerService extends IPermissionManager.Stub {
continue;
}
- if (permissionsState.hasPermission(permissionName, userId)) {
+ if (uidState.hasPermission(permissionName)) {
if (oldGrantedRestrictedPermissions.get(userId) == null) {
oldGrantedRestrictedPermissions.put(userId, new ArraySet<>());
}
oldGrantedRestrictedPermissions.get(userId).add(permissionName);
}
- final int oldFlags = permissionsState.getPermissionFlags(permissionName, userId);
+ final int oldFlags = uidState.getPermissionFlags(permissionName);
int newFlags = oldFlags;
int mask = 0;
@@ -3921,8 +3924,7 @@ 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 = permissionsState.hasPermission(permissionName,
- userId);
+ final boolean isGranted = uidState.hasPermission(permissionName);
if (!isWhitelisted && isGranted) {
mask |= PackageManager.FLAG_PERMISSION_POLICY_FIXED;
newFlags &= ~PackageManager.FLAG_PERMISSION_POLICY_FIXED;
@@ -3958,12 +3960,13 @@ public class PermissionManagerService extends IPermissionManager.Stub {
for (int j = 0; j < oldGrantedCount; j++) {
final String permission = oldPermsForUser.valueAt(j);
// Sometimes we create a new permission state instance during update.
- final PermissionsState newPermissionsState = getPermissionsState(pkg);
- if (permissionsState == null) {
- Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName());
+ final UidPermissionState newUidState = getUidState(pkg, userId);
+ if (newUidState == null) {
+ Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName()
+ + " and user " + userId);
continue;
}
- if (!newPermissionsState.hasPermission(permission, userId)) {
+ if (!newUidState.hasPermission(permission)) {
callback.onPermissionRevoked(pkg.getUid(), userId, null);
break;
}
@@ -4012,9 +4015,10 @@ public class PermissionManagerService extends IPermissionManager.Stub {
continue;
}
- PermissionsState permissionsState = getPermissionsState(deletedPs.pkg);
- if (permissionsState == null) {
- Slog.e(TAG, "Missing permissions state for " + deletedPs.pkg.getPackageName());
+ UidPermissionState uidState = getUidState(deletedPs.pkg, userId);
+ if (uidState == null) {
+ Slog.e(TAG, "Missing permissions state for " + deletedPs.pkg.getPackageName()
+ + " and user " + userId);
continue;
}
@@ -4036,25 +4040,15 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
}
- // Try to revoke as an install permission which is for all users.
// The package is gone - no need to keep flags for applying policy.
- permissionsState.updatePermissionFlags(bp, userId,
- PackageManager.MASK_PERMISSION_FLAGS_ALL, 0);
-
- if (permissionsState.revokeInstallPermission(bp)
- == PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) {
- affectedUserId = UserHandle.USER_ALL;
- }
+ uidState.updatePermissionFlags(bp, PackageManager.MASK_PERMISSION_FLAGS_ALL, 0);
// Try to revoke as a runtime permission which is per user.
- if (permissionsState.revokeRuntimePermission(bp, userId)
- == PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) {
- if (affectedUserId == UserHandle.USER_NULL) {
- affectedUserId = userId;
- } else if (affectedUserId != userId) {
- // Multiple users affected.
- affectedUserId = UserHandle.USER_ALL;
- }
+ // 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;
}
}
@@ -4062,12 +4056,12 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
@GuardedBy("mLock")
- private int[] revokeUnusedSharedUserPermissionsLocked(
- List<AndroidPackage> pkgList, PermissionsState permissionsState, int[] allUserIds) {
+ private boolean revokeUnusedSharedUserPermissionsLocked(
+ List<AndroidPackage> pkgList, UidPermissionState uidState) {
// Collect all used permissions in the UID
final ArraySet<String> usedPermissions = new ArraySet<>();
if (pkgList == null || pkgList.size() == 0) {
- return EmptyArray.INT;
+ return false;
}
for (AndroidPackage pkg : pkgList) {
if (pkg.getRequestedPermissions().isEmpty()) {
@@ -4083,44 +4077,27 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
}
- // Prune install permissions
- List<PermissionState> installPermStates = permissionsState.getInstallPermissionStates();
- final int installPermCount = installPermStates.size();
- for (int i = installPermCount - 1; i >= 0; i--) {
- PermissionState permissionState = installPermStates.get(i);
+ boolean runtimePermissionChanged = false;
+
+ // Prune permissions
+ final List<com.android.server.pm.permission.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) {
- permissionsState.revokeInstallPermission(bp);
- permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
- MASK_PERMISSION_FLAGS_ALL, 0);
- }
- }
- }
-
- int[] runtimePermissionChangedUserIds = EmptyArray.INT;
-
- // Prune runtime permissions
- for (int userId : allUserIds) {
- List<PermissionState> runtimePermStates = permissionsState
- .getRuntimePermissionStates(userId);
- final int runtimePermCount = runtimePermStates.size();
- for (int i = runtimePermCount - 1; i >= 0; i--) {
- PermissionState permissionState = runtimePermStates.get(i);
- if (!usedPermissions.contains(permissionState.getName())) {
- BasePermission bp = mSettings.getPermissionLocked(permissionState.getName());
- if (bp != null) {
- permissionsState.revokeRuntimePermission(bp, userId);
- permissionsState.updatePermissionFlags(bp, userId,
- MASK_PERMISSION_FLAGS_ALL, 0);
- runtimePermissionChangedUserIds = ArrayUtils.appendInt(
- runtimePermissionChangedUserIds, userId);
+ uidState.revokePermission(bp);
+ uidState.updatePermissionFlags(bp, MASK_PERMISSION_FLAGS_ALL, 0);
+ if (permissionState.isRuntime()) {
+ runtimePermissionChanged = true;
}
}
}
}
- return runtimePermissionChangedUserIds;
+ return runtimePermissionChanged;
}
/**
@@ -4368,15 +4345,19 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
} else {
mPackageManagerInt.forEachPackage(p -> {
- final PermissionsState permissionsState = getPermissionsState(p);
- if (permissionsState == null) {
- Slog.e(TAG, "Missing permissions state for " + p.getPackageName());
- return;
- }
- if (permissionsState.getInstallPermissionState(bp.getName()) != null) {
- permissionsState.revokeInstallPermission(bp);
- permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
- MASK_PERMISSION_FLAGS_ALL, 0);
+ 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);
+ }
}
});
}
@@ -4548,7 +4529,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
final int callingUserId = UserHandle.getUserId(callingUid);
if (hasCrossUserPermission(
- callingUid, callingUserId, userId, requireFullPermission,
+ Binder.getCallingPid(), callingUid, callingUserId, userId, requireFullPermission,
requirePermissionWhenSameUser)) {
return;
}
@@ -4575,53 +4556,79 @@ 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 (checkShell) {
- PackageManagerServiceUtils.enforceShellRestriction(mUserManagerInt,
- UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
- }
- final int callingUserId = UserHandle.getUserId(callingUid);
- if (hasCrossUserPermission(callingUid, callingUserId, userId, requireFullPermission,
- /*requirePermissionWhenSameUser= */ false)) {
+
+ if (callingUserId == userId) {
return;
}
- 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) {
+
+ // Prevent endless loop between when checking permission while checking a permission
+ if (callingPid == ActivityManagerService.MY_PID) {
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 callingUid, int callingUserId, int userId, boolean requireFullPermission,
- boolean requirePermissionWhenSameUser) {
+ private boolean hasCrossUserPermission(int callingPid, 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) {
- return hasPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+
+ if (!requireFullPermission) {
+ if (mContext.checkPermission(android.Manifest.permission.INTERACT_ACROSS_USERS,
+ callingPid, callingUid) == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
}
- return hasPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
- || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS);
- }
- private boolean hasPermission(String permission) {
- return mContext.checkCallingOrSelfPermission(permission)
- == PackageManager.PERMISSION_GRANTED;
+ return mContext.checkPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
}
private boolean isSameProfileGroup(@UserIdInt int callerUserId, @UserIdInt int userId) {
@@ -4784,62 +4791,124 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
@Nullable
- private PermissionsState getPermissionsState(@NonNull PackageSetting ps) {
- return getPermissionsState(ps.getAppId());
- }
-
- @Nullable
- private PermissionsState getPermissionsState(@NonNull AndroidPackage pkg) {
- return getPermissionsState(pkg.getUid());
- }
-
- @Nullable
- private PermissionsState getPermissionsState(int appId) {
- synchronized (mLock) {
- return mAppIdStates.get(appId);
- }
+ private UidPermissionState getUidState(@NonNull PackageSetting ps,
+ @UserIdInt int userId) {
+ return getUidState(ps.getAppId(), userId);
}
@Nullable
- private PermissionsState getOrCreatePermissionsState(@NonNull PackageSetting ps) {
- return getOrCreatePermissionsState(ps.getAppId());
+ private UidPermissionState getUidState(@NonNull AndroidPackage pkg,
+ @UserIdInt int userId) {
+ return getUidState(pkg.getUid(), userId);
}
@Nullable
- private PermissionsState getOrCreatePermissionsState(int appId) {
+ private UidPermissionState getUidState(int appId, @UserIdInt int userId) {
synchronized (mLock) {
- PermissionsState state = mAppIdStates.get(appId);
- if (state == null) {
- state = new PermissionsState();
- mAppIdStates.put(appId, state);
+ final UserPermissionState userState = mState.getUserState(userId);
+ if (userState == null) {
+ return null;
}
- return state;
+ return userState.getUidState(appId);
}
}
- private void removePermissionsState(int appId) {
+ private void removeAppState(int appId) {
synchronized (mLock) {
- mAppIdStates.remove(appId);
+ final int[] userIds = mState.getUserIds();
+ for (final int userId : userIds) {
+ final UserPermissionState userState = mState.getUserState(userId);
+ userState.removeUidState(appId);
+ }
}
}
- private void readPermissionsStateFromPackageSettings() {
+ private void readStateFromPackageSettings() {
+ final int[] userIds = getAllUserIds();
mPackageManagerInt.forEachPackageSetting(ps -> {
+ final int appId = ps.getAppId();
+ final PermissionsState permissionsState = ps.getPermissionsState();
+
synchronized (mLock) {
- mAppIdStates.put(ps.getAppId(), new PermissionsState(ps.getPermissionsState()));
+ for (final int userId : userIds) {
+ final UserPermissionState userState = mState.getOrCreateUserState(userId);
+
+ userState.setInstallPermissionsFixed(ps.name, ps.areInstallPermissionsFixed());
+ final UidPermissionState uidState = userState.getOrCreateUidState(appId);
+ uidState.reset();
+ uidState.setGlobalGids(permissionsState.getGlobalGids());
+ uidState.setMissing(permissionsState.isMissing(userId));
+ readStateFromPermissionStates(uidState,
+ permissionsState.getInstallPermissionStates(), false);
+ readStateFromPermissionStates(uidState,
+ permissionsState.getRuntimePermissionStates(userId), true);
+ }
}
});
}
- private void writePermissionsStateToPackageSettings() {
+ 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());
+ }
+ }
+
+ private void writeStateToPackageSettings() {
+ final int[] userIds = mState.getUserIds();
mPackageManagerInt.forEachPackageSetting(ps -> {
+ ps.setInstallPermissionsFixed(false);
+ final PermissionsState permissionsState = ps.getPermissionsState();
+ permissionsState.reset();
+ final int appId = ps.getAppId();
+
synchronized (mLock) {
- final PermissionsState permissionsState = mAppIdStates.get(ps.getAppId());
- if (permissionsState == null) {
- Slog.e(TAG, "Missing permissions state for " + ps.name);
- return;
+ for (final int userId : userIds) {
+ final UserPermissionState userState = mState.getUserState(userId);
+ if (userState == null) {
+ Slog.e(TAG, "Missing user state for " + userId);
+ continue;
+ }
+
+ if (userState.areInstallPermissionsFixed(ps.name)) {
+ ps.setInstallPermissionsFixed(true);
+ }
+
+ final UidPermissionState uidState = userState.getUidState(appId);
+ if (uidState == null) {
+ Slog.e(TAG, "Missing permission state for " + ps.name + " and user "
+ + userId);
+ continue;
+ }
+
+ permissionsState.setGlobalGids(uidState.getGlobalGids());
+ permissionsState.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);
+ }
+ }
}
- ps.getPermissionsState().copyFrom(permissionsState);
}
});
}
@@ -4876,12 +4945,12 @@ public class PermissionManagerService extends IPermissionManager.Stub {
PermissionManagerService.this.removeAllPermissions(pkg, chatty);
}
@Override
- public void readPermissionsStateFromPackageSettingsTEMP() {
- PermissionManagerService.this.readPermissionsStateFromPackageSettings();
+ public void readStateFromPackageSettingsTEMP() {
+ PermissionManagerService.this.readStateFromPackageSettings();
}
@Override
- public void writePermissionsStateToPackageSettingsTEMP() {
- PermissionManagerService.this.writePermissionsStateToPackageSettings();
+ public void writeStateToPackageSettingsTEMP() {
+ PermissionManagerService.this.writeStateToPackageSettings();
}
@Override
public void onUserRemoved(@UserIdInt int userId) {
@@ -4889,7 +4958,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
@Override
public void removePermissionsStateTEMP(int appId) {
- PermissionManagerService.this.removePermissionsState(appId);
+ PermissionManagerService.this.removeAppState(appId);
}
@Override
@UserIdInt
@@ -5222,10 +5291,10 @@ public class PermissionManagerService extends IPermissionManager.Stub {
@Override
public void onNewUserCreated(int userId) {
- mDefaultPermissionGrantPolicy.grantDefaultPermissions(userId);
// NOTE: This adds UPDATE_PERMISSIONS_REPLACE_PKG
PermissionManagerService.this.updateAllPermissions(StorageManager.UUID_PRIVATE_INTERNAL,
true, mDefaultPermissionCallback);
+ mDefaultPermissionGrantPolicy.grantDefaultPermissions(userId);
}
@Override
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 ca207d8f0d41..6d9bd2a1b30d 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -266,21 +266,21 @@ public abstract class PermissionManagerServiceInternal extends PermissionManager
public abstract void removeAllPermissions(@NonNull AndroidPackage pkg, boolean chatty);
/**
- * Read {@code PermissionsState} from package settings.
+ * Read permission state from package settings.
*
* TODO(zhanghai): This is a temporary method because we should not expose
* {@code PackageSetting} which is a implementation detail that permission should not know.
* Instead, it should retrieve the legacy state via a defined API.
*/
- public abstract void readPermissionsStateFromPackageSettingsTEMP();
+ public abstract void readStateFromPackageSettingsTEMP();
/**
- * Write {@code PermissionsState} from to settings.
+ * Write permission state to package settings.
*
* TODO(zhanghai): This is a temporary method and should be removed once we migrated persistence
* for permission.
*/
- public abstract void writePermissionsStateToPackageSettingsTEMP();
+ public abstract void writeStateToPackageSettingsTEMP();
/**
* Notify that a user has been removed and its permission state should be removed as well.
diff --git a/services/core/java/com/android/server/pm/permission/PermissionState.java b/services/core/java/com/android/server/pm/permission/PermissionState.java
new file mode 100644
index 000000000000..2ed9a50353d4
--- /dev/null
+++ b/services/core/java/com/android/server/pm/permission/PermissionState.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 com.android.internal.annotations.GuardedBy;
+
+/**
+ * State for a single permission.
+ */
+public final class PermissionState {
+
+ @NonNull
+ private final BasePermission mPermission;
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private boolean mRuntime;
+
+ @GuardedBy("mLock")
+ private boolean mGranted;
+
+ @GuardedBy("mLock")
+ private int mFlags;
+
+ public PermissionState(@NonNull BasePermission permission, boolean isRuntime) {
+ mPermission = permission;
+ mRuntime = isRuntime;
+ }
+
+ public PermissionState(@NonNull PermissionState other) {
+ this(other.mPermission, other.mRuntime);
+
+ mGranted = other.mGranted;
+ mFlags = other.mFlags;
+ }
+
+ @NonNull
+ public BasePermission getPermission() {
+ return mPermission;
+ }
+
+ @NonNull
+ public String getName() {
+ return mPermission.getName();
+ }
+
+ @Nullable
+ public int[] computeGids(@UserIdInt int userId) {
+ return mPermission.computeGids(userId);
+ }
+
+ public boolean isRuntime() {
+ synchronized (mLock) {
+ return mRuntime;
+ }
+ }
+
+ public boolean isGranted() {
+ synchronized (mLock) {
+ return mGranted;
+ }
+ }
+
+ public boolean grant() {
+ synchronized (mLock) {
+ if (mGranted) {
+ return false;
+ }
+ mGranted = true;
+ UidPermissionState.invalidateCache();
+ return true;
+ }
+ }
+
+ public boolean revoke() {
+ synchronized (mLock) {
+ if (!mGranted) {
+ return false;
+ }
+ mGranted = false;
+ UidPermissionState.invalidateCache();
+ return true;
+ }
+ }
+
+ public int getFlags() {
+ synchronized (mLock) {
+ return mFlags;
+ }
+ }
+
+ public boolean updateFlags(int flagMask, int flagValues) {
+ synchronized (mLock) {
+ final int newFlags = flagValues & flagMask;
+
+ // Okay to do before the modification because we hold the lock.
+ UidPermissionState.invalidateCache();
+
+ final int oldFlags = mFlags;
+ mFlags = (mFlags & ~flagMask) | newFlags;
+ return mFlags != oldFlags;
+ }
+ }
+
+ public boolean isDefault() {
+ synchronized (mLock) {
+ return !mGranted && mFlags == 0;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionsState.java b/services/core/java/com/android/server/pm/permission/PermissionsState.java
index bad59cb1b567..4fb2d5fc200e 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionsState.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionsState.java
@@ -86,6 +86,10 @@ public final class PermissionsState {
copyFrom(prototype);
}
+ public int[] getGlobalGids() {
+ return mGlobalGids;
+ }
+
/**
* Sets the global gids, applicable to all users.
*
@@ -825,7 +829,7 @@ public final class PermissionsState {
PermissionState userState = mUserStates.get(userId);
if (userState == null) {
- userState = new PermissionState(mPerm.getName());
+ userState = new PermissionState(mPerm);
mUserStates.put(userId, userState);
}
@@ -908,7 +912,7 @@ public final class PermissionsState {
}
return userState.mFlags != oldFlags;
} else if (newFlags != 0) {
- userState = new PermissionState(mPerm.getName());
+ userState = new PermissionState(mPerm);
userState.mFlags = newFlags;
mUserStates.put(userId, userState);
return true;
@@ -929,16 +933,16 @@ public final class PermissionsState {
}
public static final class PermissionState {
- private final String mName;
+ private final BasePermission mPermission;
private boolean mGranted;
private int mFlags;
- public PermissionState(String name) {
- mName = name;
+ public PermissionState(BasePermission permission) {
+ mPermission = permission;
}
public PermissionState(PermissionState other) {
- mName = other.mName;
+ mPermission = other.mPermission;
mGranted = other.mGranted;
mFlags = other.mFlags;
}
@@ -947,8 +951,12 @@ public final class PermissionsState {
return !mGranted && mFlags == 0;
}
+ public BasePermission getPermission() {
+ return mPermission;
+ }
+
public String getName() {
- return mName;
+ return mPermission.getName();
}
public boolean isGranted() {
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 c0d71ac26853..65dc320eadc2 100644
--- a/services/core/java/com/android/server/pm/permission/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/permission/TEST_MAPPING
@@ -18,21 +18,24 @@
]
},
{
- "name": "CtsAppSecurityHostTestCases",
+ "name": "CtsPermission2TestCases",
"options": [
{
- "include-filter": "android.appsecurity.cts.AppSecurityTests#rebootWithDuplicatePermission"
+ "include-filter": "android.permission2.cts.RestrictedPermissionsTest"
+ },
+ {
+ "include-filter": "android.permission.cts.PermissionMaxSdkVersionTest"
}
]
},
{
- "name": "CtsPermission2TestCases",
+ "name": "CtsPermissionHostTestCases"
+ },
+ {
+ "name": "CtsAppSecurityHostTestCases",
"options": [
{
- "include-filter": "android.permission2.cts.RestrictedPermissionsTest"
- },
- {
- "include-filter": "android.permission.cts.PermissionMaxSdkVersionTest"
+ "include-filter": "android.appsecurity.cts.AppSecurityTests#rebootWithDuplicatePermission"
}
]
},
diff --git a/services/core/java/com/android/server/pm/permission/UidPermissionState.java b/services/core/java/com/android/server/pm/permission/UidPermissionState.java
new file mode 100644
index 000000000000..4c047ffd30e8
--- /dev/null
+++ b/services/core/java/com/android/server/pm/permission/UidPermissionState.java
@@ -0,0 +1,574 @@
+/*
+ * 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.content.pm.PackageManager;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+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.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 */
+ }
+
+ public UidPermissionState(@NonNull UidPermissionState prototype) {
+ copyFrom(prototype);
+ }
+
+ /**
+ * Gets the global gids, applicable to all users.
+ */
+ @NonNull
+ public int[] getGlobalGids() {
+ return mGlobalGids;
+ }
+
+ /**
+ * 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);
+ }
+ }
+
+ static void invalidateCache() {
+ PackageManager.invalidatePackageInfoCache();
+ }
+
+ /**
+ * Initialized this instance from another one.
+ *
+ * @param other The other instance.
+ */
+ 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);
+ }
+
+ /**
+ * 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() {
+ return mMissing;
+ }
+
+ /**
+ * 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) {
+ mMissing = missing;
+ }
+
+ public boolean isPermissionReviewRequired() {
+ return mPermissionReviewRequired;
+ }
+
+ /**
+ * Gets whether the state has a given permission.
+ *
+ * @param name The permission name.
+ * @return Whether the state has the permission.
+ */
+ public boolean hasPermission(@NonNull String name) {
+ synchronized (mLock) {
+ if (mPermissions == null) {
+ return false;
+ }
+ PermissionState permissionState = mPermissions.get(name);
+ return permissionState != null && permissionState.isGranted();
+ }
+ }
+
+ /**
+ * Gets whether the state has a given install permission.
+ *
+ * @param name The permission name.
+ * @return Whether the state has the install permission.
+ */
+ public boolean hasInstallPermission(@NonNull String name) {
+ synchronized (mLock) {
+ if (mPermissions == null) {
+ return false;
+ }
+ PermissionState permissionState = mPermissions.get(name);
+ return permissionState != null && permissionState.isGranted()
+ && !permissionState.isRuntime();
+ }
+ }
+
+ /**
+ * 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 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.
+ *
+ * @deprecated Not all requested permissions may be here.
+ */
+ @Deprecated
+ public boolean hasRequestedPermission(@NonNull 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.
+ *
+ * @return The permissions or an empty set.
+ */
+ @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;
+ }
+ }
+
+ /**
+ * Gets the flags for a permission.
+ *
+ * @param name The permission name.
+ * @return The permission state or null if no such.
+ */
+ public int getPermissionFlags(@NonNull String name) {
+ PermissionState permState = getPermissionState(name);
+ if (permState != null) {
+ return permState.getFlags();
+ }
+ return 0;
+ }
+
+ /**
+ * 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.
+ */
+ public boolean updatePermissionFlags(@NonNull BasePermission permission, int flagMask,
+ int flagValues) {
+ if (flagMask == 0) {
+ 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;
+ }
+ }
+
+ /**
+ * 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.
+ */
+ @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;
+ }
+
+ /**
+ * Compute the Linux gids for all device users from the permissions
+ * granted to these users.
+ *
+ * @return The gids for all device users.
+ */
+ @NonNull
+ public int[] computeGids(@NonNull 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 = false;
+ mPermissionReviewRequired = false;
+ }
+
+ /**
+ * Gets the state for a permission or null if no such.
+ *
+ * @param name The permission name.
+ * @return The permission state.
+ */
+ @Nullable
+ public PermissionState getPermissionState(@NonNull String name) {
+ synchronized (mLock) {
+ if (mPermissions == null) {
+ return null;
+ }
+ return mPermissions.get(name);
+ }
+ }
+
+ /**
+ * Gets all permission states.
+ *
+ * @return The permission states or an empty set.
+ */
+ @NonNull
+ public List<PermissionState> getPermissionStates() {
+ synchronized (mLock) {
+ if (mPermissions == null) {
+ return Collections.emptyList();
+ }
+ return new ArrayList<>(mPermissions.values());
+ }
+ }
+
+ /**
+ * 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;
+ }
+ }
+ }
+
+ /**
+ * Grant 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}.
+ */
+ 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;
+ }
+
+ return permission.hasGids() ? PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED
+ : PERMISSION_OPERATION_SUCCESS;
+ }
+
+ /**
+ * Revoke 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}.
+ */
+ public int revokePermission(@NonNull BasePermission permission) {
+ final String permissionName = permission.getName();
+ if (!hasPermission(permissionName)) {
+ return PERMISSION_OPERATION_SUCCESS;
+ }
+
+ PermissionState permissionState;
+ synchronized (mLock) {
+ permissionState = mPermissions.get(permissionName);
+ }
+
+ if (!permissionState.revoke()) {
+ return PERMISSION_OPERATION_FAILURE;
+ }
+
+ if (permissionState.isDefault()) {
+ ensureNoPermissionState(permissionName);
+ }
+
+ 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);
+ }
+ }
+ return current;
+ }
+
+ @NonNull
+ private PermissionState ensurePermissionState(@NonNull BasePermission permission) {
+ return ensurePermissionState(permission, permission.isRuntime());
+ }
+
+ @NonNull
+ private PermissionState ensurePermissionState(@NonNull BasePermission permission,
+ boolean isRuntime) {
+ final String permissionName = permission.getName();
+ synchronized (mLock) {
+ if (mPermissions == null) {
+ mPermissions = new ArrayMap<>();
+ }
+ PermissionState permissionState = mPermissions.get(permissionName);
+ if (permissionState == null) {
+ permissionState = new PermissionState(permission, isRuntime);
+ mPermissions.put(permissionName, permissionState);
+ }
+ return permissionState;
+ }
+ }
+
+ private void ensureNoPermissionState(@NonNull String name) {
+ synchronized (mLock) {
+ if (mPermissions == null) {
+ return;
+ }
+ mPermissions.remove(name);
+ if (mPermissions.isEmpty()) {
+ mPermissions = null;
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/permission/UserPermissionState.java b/services/core/java/com/android/server/pm/permission/UserPermissionState.java
new file mode 100644
index 000000000000..7f55cb161e40
--- /dev/null
+++ b/services/core/java/com/android/server/pm/permission/UserPermissionState.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.server.pm.permission;
+
+import android.annotation.AppIdInt;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * Permission state for a user.
+ */
+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);
+ }
+ }
+
+ public void setInstallPermissionsFixed(@NonNull String packageName, boolean fixed) {
+ synchronized (mLock) {
+ if (fixed) {
+ mInstallPermissionsFixed.add(packageName);
+ } else {
+ mInstallPermissionsFixed.remove(packageName);
+ }
+ }
+ }
+
+ @Nullable
+ public UidPermissionState getUidState(@AppIdInt int appId) {
+ checkAppId(appId);
+ synchronized (mLock) {
+ 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;
+ }
+ }
+
+ public void removeUidState(@AppIdInt int appId) {
+ checkAppId(appId);
+ synchronized (mLock) {
+ mUidStates.delete(appId);
+ }
+ }
+
+ private void checkAppId(@AppIdInt int appId) {
+ if (UserHandle.getUserId(appId) != 0) {
+ throw new IllegalArgumentException(appId + " is not an app ID");
+ }
+ }
+}
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..7b6c6561ccf1
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 {
+ enforcePermissions();
+ 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 {
+ enforcePermissions();
+ 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 {
+ enforcePermissions();
+ 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/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index 7501d9fe6a7c..73322a6987df 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -57,8 +57,12 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
private static final String TAG = "TimeZoneDetectorService";
- /** A compile time switch for enabling / disabling geolocation-based time zone detection. */
- private static final boolean GEOLOCATION_TIME_ZONE_DETECTION_ENABLED = false;
+ /**
+ * 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.
+ */
+ public static final boolean GEOLOCATION_TIME_ZONE_DETECTION_ENABLED = false;
/**
* Handles the service lifecycle for {@link TimeZoneDetectorService} and
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/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 3e638502db56..740b592fda4c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2606,7 +2606,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
Slog.v(TAG_USER_LEAVING, "finish() => pause with userLeaving=false");
}
stack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */,
- null /* resuming */);
+ null /* resuming */, "finish");
}
if (endTask) {
@@ -4770,14 +4770,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
supportsEnterPipOnTaskSwitch = false;
break;
case RESUMED:
- // If the app is capable of entering PIP, we should try pausing it now
- // so it can PIP correctly.
- if (deferHidingClient) {
- getRootTask().startPausingLocked(
- mStackSupervisor.mUserLeaving /* userLeaving */,
- false /* uiSleeping */, null /* resuming */);
+ // Do nothing if currently in the process of resuming the activity. Otherwise,
+ // starting to pause it since it is not visible.
+ if (!mSetToSleep) {
break;
}
+ getRootTask().startPausingLocked(mStackSupervisor.mUserLeaving,
+ false /* uiSleeping */, null /* resuming */, "makeInvisible");
+ // fall through
case INITIALIZING:
case PAUSING:
case PAUSED:
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index ebfd0cd9fd83..15669cbe227a 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -123,7 +123,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;
@@ -3952,52 +3951,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) {
diff --git a/services/core/java/com/android/server/wm/BarController.java b/services/core/java/com/android/server/wm/BarController.java
index 8568d5fc1d64..4a90bbcc6623 100644
--- a/services/core/java/com/android/server/wm/BarController.java
+++ b/services/core/java/com/android/server/wm/BarController.java
@@ -16,88 +16,19 @@
package com.android.server.wm;
-import static com.android.server.wm.BarControllerProto.STATE;
-import static com.android.server.wm.BarControllerProto.TRANSIENT_STATE;
-
import android.annotation.NonNull;
-import android.app.StatusBarManager;
import android.graphics.Rect;
-import android.os.Handler;
-import android.os.Message;
-import android.os.SystemClock;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-import android.view.View;
-import android.view.ViewRootImpl;
-import android.view.WindowManager;
-
-import com.android.server.LocalServices;
-import com.android.server.UiThread;
-import com.android.server.statusbar.StatusBarManagerInternal;
-
-import java.io.PrintWriter;
/**
* Controls state/behavior specific to a system bar window.
*/
public class BarController {
- private static final boolean DEBUG = false;
-
- private static final int TRANSIENT_BAR_NONE = 0;
- private static final int TRANSIENT_BAR_SHOW_REQUESTED = 1;
- private static final int TRANSIENT_BAR_SHOWING = 2;
- private static final int TRANSIENT_BAR_HIDING = 3;
-
- private static final int TRANSLUCENT_ANIMATION_DELAY_MS = 1000;
-
- private static final int MSG_NAV_BAR_VISIBILITY_CHANGED = 1;
-
- protected final String mTag;
- protected final int mDisplayId;
- private final int mTransientFlag;
- private final int mUnhideFlag;
- private final int mTranslucentFlag;
- private final int mTransparentFlag;
- private final int mStatusBarManagerId;
- private final int mTranslucentWmFlag;
private final int mWindowType;
- protected final Handler mHandler;
- private final Object mServiceAquireLock = new Object();
- private StatusBarManagerInternal mStatusBarInternal;
- protected WindowState mWin;
- private @StatusBarManager.WindowVisibleState int mState =
- StatusBarManager.WINDOW_STATE_SHOWING;
- private int mTransientBarState;
- private boolean mPendingShow;
- private long mLastTranslucent;
- private boolean mShowTransparent;
- private boolean mSetUnHideFlagWhenNextTransparent;
- private boolean mNoAnimationOnNextShow;
private final Rect mContentFrame = new Rect();
- private OnBarVisibilityChangedListener mVisibilityChangeListener;
-
- BarController(String tag, int displayId, int transientFlag, int unhideFlag, int translucentFlag,
- int statusBarManagerId, int windowType, int translucentWmFlag, int transparentFlag) {
- mTag = "BarController." + tag;
- mDisplayId = displayId;
- mTransientFlag = transientFlag;
- mUnhideFlag = unhideFlag;
- mTranslucentFlag = translucentFlag;
- mStatusBarManagerId = statusBarManagerId;
+ BarController(int windowType) {
mWindowType = windowType;
- mTranslucentWmFlag = translucentWmFlag;
- mTransparentFlag = transparentFlag;
- mHandler = new BarHandler();
- }
-
- void setWindow(WindowState win) {
- if (ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL) {
- // BarController gets replaced with InsetsPolicy in the full insets mode.
- return;
- }
- mWin = win;
}
/**
@@ -109,67 +40,6 @@ public class BarController {
mContentFrame.set(frame);
}
- void setShowTransparent(boolean transparent) {
- if (transparent != mShowTransparent) {
- mShowTransparent = transparent;
- mSetUnHideFlagWhenNextTransparent = transparent;
- mNoAnimationOnNextShow = true;
- }
- }
-
- void showTransient() {
- if (mWin != null) {
- setTransientBarState(TRANSIENT_BAR_SHOW_REQUESTED);
- }
- }
-
- boolean isTransientShowing() {
- return mTransientBarState == TRANSIENT_BAR_SHOWING;
- }
-
- boolean isTransientShowRequested() {
- return mTransientBarState == TRANSIENT_BAR_SHOW_REQUESTED;
- }
-
- boolean wasRecentlyTranslucent() {
- return (SystemClock.uptimeMillis() - mLastTranslucent) < TRANSLUCENT_ANIMATION_DELAY_MS;
- }
-
- void adjustSystemUiVisibilityLw(int oldVis, int vis) {
- if (mWin != null && mTransientBarState == TRANSIENT_BAR_SHOWING
- && (vis & mTransientFlag) == 0) {
- // sysui requests hide
- setTransientBarState(TRANSIENT_BAR_HIDING);
- setBarShowingLw(false);
- } else if (mWin != null && (oldVis & mUnhideFlag) != 0 && (vis & mUnhideFlag) == 0) {
- // sysui ready to unhide
- setBarShowingLw(true);
- }
- }
-
- int applyTranslucentFlagLw(WindowState win, int vis, int oldVis) {
- if (mWin != null) {
- if (win != null) {
- int fl = PolicyControl.getWindowFlags(win, null);
- if ((fl & mTranslucentWmFlag) != 0) {
- vis |= mTranslucentFlag;
- } else {
- vis &= ~mTranslucentFlag;
- }
- if ((fl & WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
- && isTransparentAllowed(win)) {
- vis |= mTransparentFlag;
- } else {
- vis &= ~mTransparentFlag;
- }
- } else {
- vis = (vis & ~mTranslucentFlag) | (oldVis & mTranslucentFlag);
- vis = (vis & ~mTransparentFlag) | (oldVis & mTransparentFlag);
- }
- }
- return vis;
- }
-
private Rect getContentFrame(@NonNull WindowState win) {
final Rect rotatedContentFrame = win.mToken.getFixedRotationBarContentFrame(mWindowType);
return rotatedContentFrame != null ? rotatedContentFrame : mContentFrame;
@@ -188,212 +58,4 @@ public class BarController {
}
return win.letterboxNotIntersectsOrFullyContains(getContentFrame(win));
}
-
- boolean setBarShowingLw(final boolean show) {
- if (mWin == null) return false;
- if (show && mTransientBarState == TRANSIENT_BAR_HIDING) {
- mPendingShow = true;
- return false;
- }
- final boolean wasVis = mWin.isVisibleLw();
- final boolean wasAnim = mWin.isAnimatingLw();
- final boolean skipAnim = skipAnimation();
- final boolean change = show ? mWin.showLw(!mNoAnimationOnNextShow && !skipAnim)
- : mWin.hideLw(!mNoAnimationOnNextShow && !skipAnim);
- mNoAnimationOnNextShow = false;
- final int state = computeStateLw(wasVis, wasAnim, mWin, change);
- final boolean stateChanged = updateStateLw(state);
-
- if (change && (mVisibilityChangeListener != null)) {
- mHandler.obtainMessage(MSG_NAV_BAR_VISIBILITY_CHANGED, show ? 1 : 0, 0).sendToTarget();
- }
-
- return change || stateChanged;
- }
-
- void setOnBarVisibilityChangedListener(OnBarVisibilityChangedListener listener,
- boolean invokeWithState) {
- mVisibilityChangeListener = listener;
- if (invokeWithState) {
- // Optionally report the initial window state for initialization purposes
- mHandler.obtainMessage(MSG_NAV_BAR_VISIBILITY_CHANGED,
- (mState == StatusBarManager.WINDOW_STATE_SHOWING) ? 1 : 0, 0).sendToTarget();
- }
- }
-
- protected boolean skipAnimation() {
- return !mWin.isDrawn();
- }
-
- private @StatusBarManager.WindowVisibleState int computeStateLw(
- boolean wasVis, boolean wasAnim, WindowState win, boolean change) {
- if (win.isDrawn()) {
- final boolean vis = win.isVisibleLw();
- final boolean anim = win.isAnimatingLw();
- if (mState == StatusBarManager.WINDOW_STATE_HIDING && !change && !vis) {
- return StatusBarManager.WINDOW_STATE_HIDDEN;
- } else if (mState == StatusBarManager.WINDOW_STATE_HIDDEN && vis) {
- return StatusBarManager.WINDOW_STATE_SHOWING;
- } else if (change) {
- if (wasVis && vis && !wasAnim && anim) {
- return StatusBarManager.WINDOW_STATE_HIDING;
- } else {
- return StatusBarManager.WINDOW_STATE_SHOWING;
- }
- }
- }
- return mState;
- }
-
- private boolean updateStateLw(@StatusBarManager.WindowVisibleState final int state) {
- if (mWin != null && state != mState) {
- mState = state;
- if (DEBUG) Slog.d(mTag, "mState: " + StatusBarManager.windowStateToString(state));
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- StatusBarManagerInternal statusbar = getStatusBarInternal();
- if (statusbar != null) {
- statusbar.setWindowState(mDisplayId, mStatusBarManagerId, state);
- }
- }
- });
- return true;
- }
- return false;
- }
-
- boolean checkHiddenLw() {
- if (mWin != null && mWin.isDrawn()) {
- if (!mWin.isVisibleLw() && !mWin.isAnimatingLw()) {
- updateStateLw(StatusBarManager.WINDOW_STATE_HIDDEN);
- }
- if (mTransientBarState == TRANSIENT_BAR_HIDING && !mWin.isVisibleLw()) {
- // Finished animating out, clean up and reset style
- setTransientBarState(TRANSIENT_BAR_NONE);
- if (mPendingShow) {
- setBarShowingLw(true);
- mPendingShow = false;
- }
- return true;
- }
- }
- return false;
- }
-
- boolean checkShowTransientBarLw() {
- if (mTransientBarState == TRANSIENT_BAR_SHOWING) {
- if (DEBUG) Slog.d(mTag, "Not showing transient bar, already shown");
- return false;
- } else if (mTransientBarState == TRANSIENT_BAR_SHOW_REQUESTED) {
- if (DEBUG) Slog.d(mTag, "Not showing transient bar, already requested");
- return false;
- } else if (mWin == null) {
- if (DEBUG) Slog.d(mTag, "Not showing transient bar, bar doesn't exist");
- return false;
- } else if (mWin.isDisplayed()) {
- if (DEBUG) Slog.d(mTag, "Not showing transient bar, bar already visible");
- return false;
- } else {
- return true;
- }
- }
-
- int updateVisibilityLw(boolean transientAllowed, int oldVis, int vis) {
- if (mWin == null) return vis;
- if (isTransientShowing() || isTransientShowRequested()) { // transient bar requested
- if (transientAllowed) {
- vis |= mTransientFlag;
- if ((oldVis & mTransientFlag) == 0) {
- vis |= mUnhideFlag; // tell sysui we're ready to unhide
- }
- setTransientBarState(TRANSIENT_BAR_SHOWING); // request accepted
- } else {
- setTransientBarState(TRANSIENT_BAR_NONE); // request denied
- }
- }
- if (mShowTransparent) {
- vis |= mTransparentFlag;
- if (mSetUnHideFlagWhenNextTransparent) {
- vis |= mUnhideFlag;
- mSetUnHideFlagWhenNextTransparent = false;
- }
- }
- if (mTransientBarState != TRANSIENT_BAR_NONE) {
- vis |= mTransientFlag; // ignore clear requests until transition completes
- vis &= ~View.SYSTEM_UI_FLAG_LOW_PROFILE; // never show transient bars in low profile
- }
- if ((vis & mTranslucentFlag) != 0 || (oldVis & mTranslucentFlag) != 0
- || ((vis | oldVis) & mTransparentFlag) != 0) {
- mLastTranslucent = SystemClock.uptimeMillis();
- }
- return vis;
- }
-
- private void setTransientBarState(int state) {
- if (mWin != null && state != mTransientBarState) {
- if (mTransientBarState == TRANSIENT_BAR_SHOWING || state == TRANSIENT_BAR_SHOWING) {
- mLastTranslucent = SystemClock.uptimeMillis();
- }
- mTransientBarState = state;
- if (DEBUG) Slog.d(mTag, "mTransientBarState: " + transientBarStateToString(state));
- }
- }
-
- protected StatusBarManagerInternal getStatusBarInternal() {
- synchronized (mServiceAquireLock) {
- if (mStatusBarInternal == null) {
- mStatusBarInternal = LocalServices.getService(StatusBarManagerInternal.class);
- }
- return mStatusBarInternal;
- }
- }
-
- private static String transientBarStateToString(int state) {
- if (state == TRANSIENT_BAR_HIDING) return "TRANSIENT_BAR_HIDING";
- if (state == TRANSIENT_BAR_SHOWING) return "TRANSIENT_BAR_SHOWING";
- if (state == TRANSIENT_BAR_SHOW_REQUESTED) return "TRANSIENT_BAR_SHOW_REQUESTED";
- if (state == TRANSIENT_BAR_NONE) return "TRANSIENT_BAR_NONE";
- throw new IllegalArgumentException("Unknown state " + state);
- }
-
- void dumpDebug(ProtoOutputStream proto, long fieldId) {
- final long token = proto.start(fieldId);
- proto.write(STATE, mState);
- proto.write(TRANSIENT_STATE, mTransientBarState);
- proto.end(token);
- }
-
- void dump(PrintWriter pw, String prefix) {
- if (mWin != null) {
- pw.print(prefix); pw.println(mTag);
- pw.print(prefix); pw.print(" "); pw.print("mState"); pw.print('=');
- pw.println(StatusBarManager.windowStateToString(mState));
- pw.print(prefix); pw.print(" "); pw.print("mTransientBar"); pw.print('=');
- pw.println(transientBarStateToString(mTransientBarState));
- pw.print(prefix); pw.print(" mContentFrame="); pw.println(mContentFrame);
- }
- }
-
- private class BarHandler extends Handler {
- BarHandler() {
- super(UiThread.getHandler().getLooper());
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_NAV_BAR_VISIBILITY_CHANGED:
- final boolean visible = msg.arg1 != 0;
- if (mVisibilityChangeListener != null) {
- mVisibilityChangeListener.onBarVisibilityChanged(visible);
- }
- break;
- }
- }
- }
-
- interface OnBarVisibilityChangedListener {
- void onBarVisibilityChanged(boolean visible);
- }
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 2ca849de7f22..0be5cbe5199c 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_BEHIND;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
@@ -192,7 +194,6 @@ import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.SurfaceSession;
import android.view.View;
-import android.view.ViewRootImpl;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowManagerPolicyConstants.PointerEventListener;
@@ -465,6 +466,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.
*
@@ -2328,7 +2332,21 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return getLastOrientation();
}
}
- return super.getOrientation();
+
+ final int orientation = super.getOrientation();
+ if (orientation != SCREEN_ORIENTATION_UNSET && orientation != SCREEN_ORIENTATION_BEHIND) {
+ ProtoLog.v(WM_DEBUG_ORIENTATION,
+ "App is requesting an orientation, return %d for display id=%d",
+ orientation, mDisplayId);
+ return orientation;
+ }
+
+ ProtoLog.v(WM_DEBUG_ORIENTATION,
+ "No app is requesting an orientation, return %d for display id=%d",
+ getLastOrientation(), mDisplayId);
+ // The next app has not been requested to be visible, so we keep the current orientation
+ // to prevent freezing/unfreezing the display too early.
+ return getLastOrientation();
}
void updateDisplayInfo() {
@@ -3203,6 +3221,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;
@@ -3215,6 +3237,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");
@@ -3744,7 +3779,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
void statusBarVisibilityChanged(int visibility) {
mLastStatusBarVisibility = visibility;
- visibility = getDisplayPolicy().adjustSystemUiVisibilityLw(visibility);
updateStatusBarVisibilityLocked(visibility);
}
@@ -3769,32 +3803,16 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
void updateSystemUiVisibility(int visibility, int globalDiff) {
forAllWindows(w -> {
- try {
- 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;
- }
- if ((newValue != curValue || w.mAttrs.hasSystemUiListeners)
- && ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL) {
- w.mClient.dispatchSystemUiVisibilityChanged(w.mSeq,
- visibility, newValue, diff);
- }
- } catch (RemoteException e) {
- // so sorry
+ 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 reevaluateStatusBarVisibility() {
- int visibility = getDisplayPolicy().adjustSystemUiVisibilityLw(mLastStatusBarVisibility);
- if (updateStatusBarVisibilityLocked(visibility)) {
- mWmService.mWindowPlacerLocked.requestTraversal();
- }
- }
-
void onWindowFreezeTimeout() {
Slog.w(TAG_WM, "Window freeze timeout expired.");
mWmService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 779f6b2d30cc..572c9b306047 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -57,8 +57,6 @@ import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
-import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
@@ -66,13 +64,11 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BA
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
@@ -134,14 +130,13 @@ import android.graphics.Region;
import android.hardware.input.InputManager;
import android.hardware.power.Boost;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.ArraySet;
-import android.util.IntArray;
-import android.util.Pair;
import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SparseArray;
@@ -199,7 +194,6 @@ import java.util.function.Consumer;
*/
public class DisplayPolicy {
private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplayPolicy" : TAG_WM;
- private static final boolean DEBUG = false;
private static final boolean ALTERNATE_CAR_MODE_NAV_SIZE = false;
@@ -221,18 +215,6 @@ public class DisplayPolicy {
/** Use the transit animation in style resource (see {@link #selectAnimation}). */
static final int ANIMATION_STYLEABLE = 0;
- /**
- * These are the system UI flags that, when changing, can cause the layout
- * of the screen to change.
- */
- private static final int SYSTEM_UI_CHANGING_LAYOUT =
- View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
- | View.SYSTEM_UI_FLAG_FULLSCREEN
- | View.STATUS_BAR_TRANSLUCENT
- | View.NAVIGATION_BAR_TRANSLUCENT
- | View.STATUS_BAR_TRANSPARENT
- | View.NAVIGATION_BAR_TRANSPARENT;
-
private static final int[] SHOW_TYPES_FOR_SWIPE = {ITYPE_NAVIGATION_BAR, ITYPE_STATUS_BAR};
private static final int[] SHOW_TYPES_FOR_PANIC = {ITYPE_NAVIGATION_BAR};
@@ -327,21 +309,10 @@ public class DisplayPolicy {
private boolean mLastImmersiveMode;
- private final StatusBarController mStatusBarController;
-
+ private StatusBarManagerInternal mStatusBarInternal;
+ private final BarController mStatusBarController;
private final BarController mNavigationBarController;
- private final BarController.OnBarVisibilityChangedListener mNavBarVisibilityListener =
- new BarController.OnBarVisibilityChangedListener() {
- @Override
- public void onBarVisibilityChanged(boolean visible) {
- if (mAccessibilityManager == null) {
- return;
- }
- mAccessibilityManager.notifyAccessibilityButtonVisibilityChanged(visible);
- }
- };
-
// The windows we were told about in focusChanged.
private WindowState mFocusedWindow;
private WindowState mLastFocusedWindow;
@@ -353,15 +324,8 @@ public class DisplayPolicy {
private boolean mLastNavVisible;
private boolean mLastNavTranslucent;
private boolean mLastNavAllowedHidden;
- private boolean mLastNotificationShadeForcesShowingNavigation;
-
- int mLastSystemUiFlags;
- // Bits that we are in the process of clearing, so we want to prevent
- // them from being set by applications until everything has been updated
- // to have them clear.
- private int mResettingSystemUiFlags = 0;
- // Bits that we are currently always keeping cleared.
- private int mForceClearedSystemUiFlags = 0;
+
+ private int mLastDisableFlags;
private int mLastAppearance;
private int mLastFullscreenAppearance;
private int mLastDockedAppearance;
@@ -430,6 +394,8 @@ public class DisplayPolicy {
private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver;
+ private final WindowManagerInternal.AppTransitionListener mAppTransitionListener;
+
private class PolicyHandler extends Handler {
PolicyHandler(Looper looper) {
@@ -472,16 +438,9 @@ public class DisplayPolicy {
mLock = service.getWindowManagerLock();
final int displayId = displayContent.getDisplayId();
- mStatusBarController = new StatusBarController(displayId);
- mNavigationBarController = new BarController("NavigationBar",
- displayId,
- View.NAVIGATION_BAR_TRANSIENT,
- View.NAVIGATION_BAR_UNHIDE,
- View.NAVIGATION_BAR_TRANSLUCENT,
- StatusBarManager.WINDOW_NAVIGATION_BAR,
- TYPE_NAVIGATION_BAR,
- FLAG_TRANSLUCENT_NAVIGATION,
- View.NAVIGATION_BAR_TRANSPARENT);
+
+ mStatusBarController = new BarController(TYPE_STATUS_BAR);
+ mNavigationBarController = new BarController(TYPE_NAVIGATION_BAR);
final Resources r = mContext.getResources();
mCarDockEnablesAccelerometer = r.getBoolean(R.bool.config_carDockEnablesAccelerometer);
@@ -612,8 +571,58 @@ public class DisplayPolicy {
}
});
displayContent.registerPointerEventListener(mSystemGestures);
- displayContent.mAppTransition.registerListenerLocked(
- mStatusBarController.getAppTransitionListener());
+ mAppTransitionListener = new WindowManagerInternal.AppTransitionListener() {
+
+ private Runnable mAppTransitionPending = () -> {
+ StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
+ if (statusBar != null) {
+ statusBar.appTransitionPending(displayId);
+ }
+ };
+
+ private Runnable mAppTransitionCancelled = () -> {
+ StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
+ if (statusBar != null) {
+ statusBar.appTransitionCancelled(displayId);
+ }
+ };
+
+ private Runnable mAppTransitionFinished = () -> {
+ StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
+ if (statusBar != null) {
+ statusBar.appTransitionFinished(displayId);
+ }
+ };
+
+ @Override
+ public void onAppTransitionPendingLocked() {
+ mHandler.post(mAppTransitionPending);
+ }
+
+ @Override
+ public int onAppTransitionStartingLocked(int transit, long duration,
+ long statusBarAnimationStartTime, long statusBarAnimationDuration) {
+ mHandler.post(() -> {
+ StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
+ if (statusBar != null) {
+ statusBar.appTransitionStarting(mContext.getDisplayId(),
+ statusBarAnimationStartTime, statusBarAnimationDuration);
+ }
+ });
+ return 0;
+ }
+
+ @Override
+ public void onAppTransitionCancelledLocked(int transit) {
+ mHandler.post(mAppTransitionCancelled);
+ }
+
+ @Override
+ public void onAppTransitionFinishedLocked(IBinder token) {
+ mHandler.post(mAppTransitionFinished);
+ }
+ };
+ displayContent.mAppTransition.registerListenerLocked(mAppTransitionListener);
mImmersiveModeConfirmation = new ImmersiveModeConfirmation(mContext, looper,
mService.mVrModeEnabled);
@@ -1066,7 +1075,6 @@ public class DisplayPolicy {
break;
case TYPE_STATUS_BAR:
mStatusBar = win;
- mStatusBarController.setWindow(win);
final TriConsumer<DisplayFrames, WindowState, Rect> frameProvider =
(displayFrames, windowState, rect) -> {
rect.bottom = rect.top + getStatusBarHeight(displayFrames);
@@ -1077,9 +1085,6 @@ public class DisplayPolicy {
break;
case TYPE_NAVIGATION_BAR:
mNavigationBar = win;
- mNavigationBarController.setWindow(win);
- mNavigationBarController.setOnBarVisibilityChangedListener(
- mNavBarVisibilityListener, true);
mDisplayContent.setInsetProvider(ITYPE_NAVIGATION_BAR, win,
(displayFrames, windowState, inOutFrame) -> {
@@ -1135,12 +1140,10 @@ public class DisplayPolicy {
switch (insetsType) {
case ITYPE_STATUS_BAR:
mStatusBarAlt = win;
- mStatusBarController.setWindow(mStatusBarAlt);
mStatusBarAltPosition = getAltBarPosition(attrs);
break;
case ITYPE_NAVIGATION_BAR:
mNavigationBarAlt = win;
- mNavigationBarController.setWindow(mNavigationBarAlt);
mNavigationBarAltPosition = getAltBarPosition(attrs);
break;
}
@@ -1210,12 +1213,10 @@ public class DisplayPolicy {
if (mStatusBar == win || mStatusBarAlt == win) {
mStatusBar = null;
mStatusBarAlt = null;
- mStatusBarController.setWindow(null);
mDisplayContent.setInsetProvider(ITYPE_STATUS_BAR, null, null);
} else if (mNavigationBar == win || mNavigationBarAlt == win) {
mNavigationBar = null;
mNavigationBarAlt = null;
- mNavigationBarController.setWindow(null);
mDisplayContent.setInsetProvider(ITYPE_NAVIGATION_BAR, null, null);
} else if (mNotificationShade == win) {
mNotificationShade = null;
@@ -1235,7 +1236,7 @@ public class DisplayPolicy {
}
@VisibleForTesting
- StatusBarController getStatusBarController() {
+ BarController getStatusBarController() {
return mStatusBarController;
}
@@ -1368,25 +1369,6 @@ public class DisplayPolicy {
}
/**
- * Called when a new system UI visibility is being reported, allowing
- * the policy to adjust what is actually reported.
- * @param visibility The raw visibility reported by the status bar.
- * @return The new desired visibility.
- */
- public int adjustSystemUiVisibilityLw(int visibility) {
- mStatusBarController.adjustSystemUiVisibilityLw(mLastSystemUiFlags, visibility);
- mNavigationBarController.adjustSystemUiVisibilityLw(mLastSystemUiFlags, visibility);
-
- // Reset any bits in mForceClearingStatusBarVisibility that
- // are now clear.
- mResettingSystemUiFlags &= visibility;
- // Clear any bits in the new visibility that are currently being
- // force cleared, before reporting it.
- return visibility & ~mResettingSystemUiFlags
- & ~mForceClearedSystemUiFlags;
- }
-
- /**
* @return true if the system bars are forced to stay visible
*/
public boolean areSystemBarsForcedShownLw(WindowState windowState) {
@@ -1481,16 +1463,6 @@ public class DisplayPolicy {
return mForceShowSystemBars;
}
- private final Runnable mClearHideNavigationFlag = new Runnable() {
- @Override
- public void run() {
- synchronized (mLock) {
- mForceClearedSystemUiFlags &= ~View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
- mDisplayContent.reevaluateStatusBarVisibility();
- }
- }
- };
-
/**
* Input handler used while nav bar is hidden. Captures any touch on the screen,
* to determine when the nav bar should be shown and prevent applications from
@@ -1515,32 +1487,6 @@ public class DisplayPolicy {
return;
}
showSystemBars();
- // Any user activity always causes us to show the
- // navigation controls, if they had been hidden.
- // We also clear the low profile and only content
- // flags so that tapping on the screen will atomically
- // restore all currently hidden screen decorations.
- int newVal = mResettingSystemUiFlags
- | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
- | View.SYSTEM_UI_FLAG_LOW_PROFILE
- | View.SYSTEM_UI_FLAG_FULLSCREEN;
- if (mResettingSystemUiFlags != newVal) {
- mResettingSystemUiFlags = newVal;
- changed = true;
- }
- // We don't allow the system's nav bar to be hidden
- // again for 1 second, to prevent applications from
- // spamming us and keeping it from being shown.
- newVal = mForceClearedSystemUiFlags
- | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
- if (mForceClearedSystemUiFlags != newVal) {
- mForceClearedSystemUiFlags = newVal;
- changed = true;
- mHandler.postDelayed(mClearHideNavigationFlag, 1000);
- }
- if (changed) {
- mDisplayContent.reevaluateStatusBarVisibility();
- }
}
}
}
@@ -1594,12 +1540,12 @@ public class DisplayPolicy {
contentFrame -> layoutNavigationBar(displayFrames,
mDisplayContent.getConfiguration().uiMode, mLastNavVisible,
mLastNavTranslucent, mLastNavAllowedHidden,
- mLastNotificationShadeForcesShowingNavigation, contentFrame));
+ contentFrame));
}
if (mStatusBar != null) {
simulateLayoutDecorWindow(mStatusBar, displayFrames, insetsState,
simulatedWindowFrames, barContentFrames,
- contentFrame -> layoutStatusBar(displayFrames, mLastSystemUiFlags,
+ contentFrame -> layoutStatusBar(displayFrames, mLastAppearance,
contentFrame));
}
layoutScreenDecorWindows(displayFrames, simulatedWindowFrames);
@@ -1621,25 +1567,17 @@ public class DisplayPolicy {
// For purposes of putting out fake window up to steal focus, we will
// drive nav being hidden only by whether it is requested.
- final int sysui = mLastSystemUiFlags;
+ final int appearance = mLastAppearance;
final int behavior = mLastBehavior;
final InsetsSourceProvider provider =
mDisplayContent.getInsetsStateController().peekSourceProvider(ITYPE_NAVIGATION_BAR);
boolean navVisible = provider != null ? provider.isClientVisible()
: InsetsState.getDefaultVisibility(ITYPE_NAVIGATION_BAR);
- boolean navTranslucent = (sysui
- & (View.NAVIGATION_BAR_TRANSLUCENT | View.NAVIGATION_BAR_TRANSPARENT)) != 0;
- boolean immersive = (sysui & View.SYSTEM_UI_FLAG_IMMERSIVE) != 0
- || (behavior & BEHAVIOR_SHOW_BARS_BY_SWIPE) != 0;
- boolean immersiveSticky = (sysui & View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0
- || (behavior & BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE) != 0;
+ boolean navTranslucent = (appearance & APPEARANCE_OPAQUE_NAVIGATION_BARS) == 0;
+ boolean immersive = (behavior & BEHAVIOR_SHOW_BARS_BY_SWIPE) != 0;
+ boolean immersiveSticky = (behavior & BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE) != 0;
boolean navAllowedHidden = immersive || immersiveSticky;
navTranslucent &= !immersiveSticky; // transient trumps translucent
- boolean isKeyguardShowing = isKeyguardShowing() && !isKeyguardOccluded();
- boolean notificationShadeForcesShowingNavigation =
- !isKeyguardShowing && mNotificationShade != null
- && (mNotificationShade.getAttrs().privateFlags
- & PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION) != 0;
updateHideNavInputEventReceiver();
@@ -1647,21 +1585,15 @@ public class DisplayPolicy {
// be hidden (because of the screen aspect ratio), then take that into account.
navVisible |= !canHideNavigationBar();
- boolean updateSysUiVisibility = layoutNavigationBar(displayFrames, uiMode, navVisible,
- navTranslucent, navAllowedHidden, notificationShadeForcesShowingNavigation,
- null /* simulatedContentFrame */);
+ layoutNavigationBar(displayFrames, uiMode, navVisible,
+ navTranslucent, navAllowedHidden, null /* simulatedContentFrame */);
if (DEBUG_LAYOUT) Slog.i(TAG, "mDock rect:" + displayFrames.mDock);
- updateSysUiVisibility |= layoutStatusBar(displayFrames, sysui,
- null /* simulatedContentFrame */);
- if (updateSysUiVisibility) {
- updateSystemUiVisibilityLw();
- }
+ layoutStatusBar(displayFrames, appearance, null /* simulatedContentFrame */);
layoutScreenDecorWindows(displayFrames, null /* simulatedFrames */);
postAdjustDisplayFrames(displayFrames);
mLastNavVisible = navVisible;
mLastNavTranslucent = navTranslucent;
mLastNavAllowedHidden = navAllowedHidden;
- mLastNotificationShadeForcesShowingNavigation = notificationShadeForcesShowingNavigation;
}
void updateHideNavInputEventReceiver() {
@@ -1825,11 +1757,11 @@ public class DisplayPolicy {
displayFrames.mContent.set(dockFrame);
}
- private boolean layoutStatusBar(DisplayFrames displayFrames, int sysui,
+ private void layoutStatusBar(DisplayFrames displayFrames, int appearance,
Rect simulatedContentFrame) {
// decide where the status bar goes ahead of time
if (mStatusBar == null) {
- return false;
+ return;
}
// apply any status bar insets
getRotatedWindowBounds(displayFrames, mStatusBar, sTmpStatusFrame);
@@ -1860,10 +1792,9 @@ public class DisplayPolicy {
mStatusBarController.setContentFrame(sTmpRect);
}
- boolean statusBarTransient = (sysui & View.STATUS_BAR_TRANSIENT) != 0
- || mDisplayContent.getInsetsPolicy().isTransient(ITYPE_STATUS_BAR);
- boolean statusBarTranslucent = (sysui
- & (View.STATUS_BAR_TRANSLUCENT | View.STATUS_BAR_TRANSPARENT)) != 0;
+ boolean statusBarTransient =
+ mDisplayContent.getInsetsPolicy().isTransient(ITYPE_STATUS_BAR);
+ boolean statusBarTranslucent = (appearance & APPEARANCE_OPAQUE_STATUS_BARS) == 0;
// If the status bar is hidden, we don't want to cause windows behind it to scroll.
if (mStatusBar.isVisibleLw() && !statusBarTransient) {
@@ -1879,8 +1810,7 @@ public class DisplayPolicy {
"dock=%s content=%s cur=%s", dockFrame.toString(),
displayFrames.mContent.toString(), displayFrames.mCurrent.toString()));
- if (!statusBarTranslucent && !mStatusBarController.wasRecentlyTranslucent()
- && !mStatusBar.isAnimatingLw()) {
+ if (!statusBarTranslucent && !mStatusBar.isAnimatingLw()) {
// If the opaque status bar is currently requested to be visible, and not in the
// process of animating on or off, then we can tell the app that it is covered by
@@ -1888,18 +1818,17 @@ public class DisplayPolicy {
displayFrames.mSystem.top = displayFrames.mStable.top;
}
}
- return mStatusBarController.checkHiddenLw();
}
- private boolean layoutNavigationBar(DisplayFrames displayFrames, int uiMode, boolean navVisible,
- boolean navTranslucent, boolean navAllowedHidden,
- boolean statusBarForcesShowingNavigation, Rect simulatedContentFrame) {
+ private void layoutNavigationBar(DisplayFrames displayFrames, int uiMode, boolean navVisible,
+ boolean navTranslucent, boolean navAllowedHidden, Rect simulatedContentFrame) {
if (mNavigationBar == null) {
- return false;
+ return;
}
final Rect navigationFrame = sTmpNavFrame;
- boolean transientNavBarShowing = mNavigationBarController.isTransientShowing();
+ boolean navBarTransient =
+ mDisplayContent.getInsetsPolicy().isTransient(ITYPE_NAVIGATION_BAR);
// Force the navigation bar to its appropriate place and size. We need to do this directly,
// instead of relying on it to bubble up from the nav bar, because this needs to change
// atomically with screen rotations.
@@ -1924,18 +1853,11 @@ public class DisplayPolicy {
- getNavigationBarHeight(rotation, uiMode);
navigationFrame.top = topNavBar;
displayFrames.mStable.bottom = displayFrames.mStableFullscreen.bottom = top;
- if (transientNavBarShowing) {
- mNavigationBarController.setBarShowingLw(true);
- } else if (navVisible) {
- mNavigationBarController.setBarShowingLw(true);
+ if (navVisible && !navBarTransient) {
dockFrame.bottom = displayFrames.mRestricted.bottom = top;
- } else {
- // We currently want to hide the navigation UI - unless we expanded the status bar.
- mNavigationBarController.setBarShowingLw(statusBarForcesShowingNavigation);
}
if (navVisible && !navTranslucent && !navAllowedHidden
- && !mNavigationBar.isAnimatingLw()
- && !mNavigationBarController.wasRecentlyTranslucent()) {
+ && !mNavigationBar.isAnimatingLw()) {
// If the opaque nav bar is currently requested to be visible and not in the process
// of animating on or off, then we can tell the app that it is covered by it.
displayFrames.mSystem.bottom = top;
@@ -1946,18 +1868,11 @@ public class DisplayPolicy {
- getNavigationBarWidth(rotation, uiMode);
navigationFrame.left = left;
displayFrames.mStable.right = displayFrames.mStableFullscreen.right = left;
- if (transientNavBarShowing) {
- mNavigationBarController.setBarShowingLw(true);
- } else if (navVisible) {
- mNavigationBarController.setBarShowingLw(true);
+ if (navVisible && !navBarTransient) {
dockFrame.right = displayFrames.mRestricted.right = left;
- } else {
- // We currently want to hide the navigation UI - unless we expanded the status bar.
- mNavigationBarController.setBarShowingLw(statusBarForcesShowingNavigation);
}
if (navVisible && !navTranslucent && !navAllowedHidden
- && !mNavigationBar.isAnimatingLw()
- && !mNavigationBarController.wasRecentlyTranslucent()) {
+ && !mNavigationBar.isAnimatingLw()) {
// If the nav bar is currently requested to be visible, and not in the process of
// animating on or off, then we can tell the app that it is covered by it.
displayFrames.mSystem.right = left;
@@ -1968,18 +1883,11 @@ public class DisplayPolicy {
+ getNavigationBarWidth(rotation, uiMode);
navigationFrame.right = right;
displayFrames.mStable.left = displayFrames.mStableFullscreen.left = right;
- if (transientNavBarShowing) {
- mNavigationBarController.setBarShowingLw(true);
- } else if (navVisible) {
- mNavigationBarController.setBarShowingLw(true);
+ if (navVisible && !navBarTransient) {
dockFrame.left = displayFrames.mRestricted.left = right;
- } else {
- // We currently want to hide the navigation UI - unless we expanded the status bar.
- mNavigationBarController.setBarShowingLw(statusBarForcesShowingNavigation);
}
if (navVisible && !navTranslucent && !navAllowedHidden
- && !mNavigationBar.isAnimatingLw()
- && !mNavigationBarController.wasRecentlyTranslucent()) {
+ && !mNavigationBar.isAnimatingLw()) {
// If the nav bar is currently requested to be visible, and not in the process of
// animating on or off, then we can tell the app that it is covered by it.
displayFrames.mSystem.left = right;
@@ -2008,7 +1916,6 @@ public class DisplayPolicy {
}
if (DEBUG_LAYOUT) Slog.i(TAG, "mNavigationBar frame: " + navigationFrame);
- return mNavigationBarController.checkHiddenLw();
}
private boolean canReceiveInput(WindowState win) {
@@ -2059,9 +1966,6 @@ public class DisplayPolicy {
dcf.setEmpty();
windowFrames.setParentFrameWasClippedByDisplayCutout(false);
- final boolean hasNavBar = hasNavigationBar() && mNavigationBar != null
- && mNavigationBar.isVisibleLw();
-
final int adjust = sim & SOFT_INPUT_MASK_ADJUST;
final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN;
@@ -2407,53 +2311,27 @@ public class DisplayPolicy {
+ " top=" + mTopFullscreenOpaqueWindowState);
final boolean forceShowStatusBar = (getStatusBar().getAttrs().privateFlags
& PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR) != 0;
- final boolean notificationShadeForcesShowingNavigation =
- mNotificationShade != null
- && (mNotificationShade.getAttrs().privateFlags
- & PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION) != 0;
boolean topAppHidesStatusBar = topAppHidesStatusBar();
if (mForceStatusBar || forceShowStatusBar) {
if (DEBUG_LAYOUT) Slog.v(TAG, "Showing status bar: forced");
- if (mStatusBarController.setBarShowingLw(true)) {
- changes |= FINISH_LAYOUT_REDO_LAYOUT;
- }
// Maintain fullscreen layout until incoming animation is complete.
topIsFullscreen = mTopIsFullscreen && mStatusBar.isAnimatingLw();
- // Transient status bar is not allowed if notification shade is expecting the
- // navigation keys from the user.
- if (notificationShadeForcesShowingNavigation
- && mStatusBarController.isTransientShowing()) {
- mStatusBarController.updateVisibilityLw(false /*transientAllowed*/,
- mLastSystemUiFlags, mLastSystemUiFlags);
- }
} else if (mTopFullscreenOpaqueWindowState != null) {
topIsFullscreen = topAppHidesStatusBar;
// The subtle difference between the window for mTopFullscreenOpaqueWindowState
// and mTopIsFullscreen is that mTopIsFullscreen is set only if the window
// has the FLAG_FULLSCREEN set. Not sure if there is another way that to be the
// case though.
- if (mStatusBarController.isTransientShowing()) {
- if (mStatusBarController.setBarShowingLw(true)) {
- changes |= FINISH_LAYOUT_REDO_LAYOUT;
- }
- } else if (topIsFullscreen && !mDisplayContent.getDefaultTaskDisplayArea()
+ if (!topIsFullscreen || mDisplayContent.getDefaultTaskDisplayArea()
.isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)) {
- if (DEBUG_LAYOUT) Slog.v(TAG, "** HIDING status bar");
- if (mStatusBarController.setBarShowingLw(false)) {
- changes |= FINISH_LAYOUT_REDO_LAYOUT;
- } else {
- if (DEBUG_LAYOUT) Slog.v(TAG, "Status bar already hiding");
- }
- } else {
- if (DEBUG_LAYOUT) Slog.v(TAG, "** SHOWING status bar: top is not fullscreen");
- if (mStatusBarController.setBarShowingLw(true)) {
- changes |= FINISH_LAYOUT_REDO_LAYOUT;
- }
topAppHidesStatusBar = false;
}
}
- mStatusBarController.setTopAppHidesStatusBar(topAppHidesStatusBar);
+ StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
+ if (statusBar != null) {
+ statusBar.setTopAppHidesStatusBar(topAppHidesStatusBar);
+ }
}
if (mTopIsFullscreen != topIsFullscreen) {
@@ -2464,7 +2342,7 @@ public class DisplayPolicy {
mTopIsFullscreen = topIsFullscreen;
}
- if ((updateSystemUiVisibilityLw() & SYSTEM_UI_CHANGING_LAYOUT) != 0) {
+ if (updateSystemUiVisibilityLw()) {
// If the navigation bar has been hidden or shown, we need to do another
// layout pass to update that window.
changes |= FINISH_LAYOUT_REDO_LAYOUT;
@@ -2497,7 +2375,6 @@ public class DisplayPolicy {
Slog.d(TAG, "attr: " + attrs + " request: " + request);
}
return (fl & LayoutParams.FLAG_FULLSCREEN) != 0
- || (sysui & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0
|| (request != null && !request.isVisible());
}
@@ -2910,7 +2787,7 @@ public class DisplayPolicy {
if (mDisplayContent.isDefaultDisplay) {
mService.mPolicy.onDefaultDisplayFocusChangedLw(newFocus);
}
- if ((updateSystemUiVisibilityLw() & SYSTEM_UI_CHANGING_LAYOUT) != 0) {
+ if (updateSystemUiVisibilityLw()) {
// If the navigation bar has been hidden or shown, we need to do another
// layout pass to update that window.
return FINISH_LAYOUT_REDO_LAYOUT;
@@ -2977,17 +2854,20 @@ public class DisplayPolicy {
}
void resetSystemUiVisibilityLw() {
- mLastSystemUiFlags = 0;
+ mLastDisableFlags = 0;
updateSystemUiVisibilityLw();
}
- int updateSystemUiVisibilityLw() {
+ /**
+ * @return {@code true} if the update may affect the layout.
+ */
+ boolean updateSystemUiVisibilityLw() {
// If there is no window focused, there will be nobody to handle the events
// anyway, so just hang on in whatever state we're in until things settle down.
WindowState winCandidate = mFocusedWindow != null ? mFocusedWindow
: mTopFullscreenOpaqueWindowState;
if (winCandidate == null) {
- return 0;
+ return false;
}
// The immersive mode confirmation should never affect the system bar visibility, otherwise
@@ -3003,7 +2883,7 @@ public class DisplayPolicy {
: lastFocusCanReceiveKeys ? mLastFocusedWindow
: mTopFullscreenOpaqueWindowState;
if (winCandidate == null) {
- return 0;
+ return false;
}
}
final WindowState win = winCandidate;
@@ -3011,17 +2891,9 @@ public class DisplayPolicy {
mDisplayContent.getInsetsPolicy().updateBarControlTarget(win);
- int tmpVisibility = PolicyControl.getSystemUiVisibility(win, null)
- & ~mResettingSystemUiFlags
- & ~mForceClearedSystemUiFlags;
- if (mForcingShowNavBar && win.getSurfaceLayer() < mForcingShowNavBarLayer) {
- tmpVisibility
- &= ~PolicyControl.adjustClearableFlags(win, View.SYSTEM_UI_CLEARABLE_FLAGS);
- }
-
- final int fullscreenAppearance = updateLightStatusBarAppearanceLw(0 /* vis */,
+ final int fullscreenAppearance = updateLightStatusBarLw(0 /* vis */,
mTopFullscreenOpaqueWindowState, mTopFullscreenOpaqueOrDimmingWindowState);
- final int dockedAppearance = updateLightStatusBarAppearanceLw(0 /* vis */,
+ final int dockedAppearance = updateLightStatusBarLw(0 /* vis */,
mTopDockedOpaqueWindowState, mTopDockedOpaqueOrDimmingWindowState);
final boolean inSplitScreen =
mService.mRoot.getDefaultTaskDisplayArea().isSplitScreenModeActivated();
@@ -3034,32 +2906,24 @@ public class DisplayPolicy {
mService.getStackBounds(inSplitScreen ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
: WINDOWING_MODE_FULLSCREEN,
ACTIVITY_TYPE_UNDEFINED, mNonDockedStackBounds);
- final Pair<Integer, WindowState> result =
- updateSystemBarsLw(win, mLastSystemUiFlags, tmpVisibility);
- final int visibility = result.first;
- final WindowState navColorWin = result.second;
+ final int disableFlags = win.getSystemUiVisibility() & StatusBarManager.DISABLE_MASK;
+ final int opaqueAppearance = updateSystemBarsLw(win, disableFlags);
+ final WindowState navColorWin = chooseNavigationColorWindowLw(
+ mTopFullscreenOpaqueWindowState, mTopFullscreenOpaqueOrDimmingWindowState,
+ mDisplayContent.mInputMethodWindow, mNavigationBarPosition);
final boolean isNavbarColorManagedByIme =
navColorWin != null && navColorWin == mDisplayContent.mInputMethodWindow;
- final int opaqueAppearance = InsetsFlags.getAppearance(visibility)
- & (APPEARANCE_OPAQUE_STATUS_BARS | APPEARANCE_OPAQUE_NAVIGATION_BARS);
- final int appearance = updateLightNavigationBarAppearanceLw(
+ final int appearance = updateLightNavigationBarLw(
win.mAttrs.insetsFlags.appearance, mTopFullscreenOpaqueWindowState,
mTopFullscreenOpaqueOrDimmingWindowState,
mDisplayContent.mInputMethodWindow, navColorWin) | opaqueAppearance;
- final int diff = visibility ^ mLastSystemUiFlags;
- final InsetsPolicy insetsPolicy = getInsetsPolicy();
- final boolean isFullscreen = (visibility & (View.SYSTEM_UI_FLAG_FULLSCREEN
- | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)) != 0
- || (PolicyControl.getWindowFlags(win, win.mAttrs) & FLAG_FULLSCREEN) != 0
- || (getStatusBar() != null && insetsPolicy.isHidden(ITYPE_STATUS_BAR))
- || (getNavigationBar() != null && insetsPolicy.isHidden(
- ITYPE_NAVIGATION_BAR));
+ final InsetsState requestedInsets = win.getRequestedInsetsState();
final int behavior = win.mAttrs.insetsFlags.behavior;
- final boolean isImmersive = (visibility & (View.SYSTEM_UI_FLAG_IMMERSIVE
- | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)) != 0
- || behavior == BEHAVIOR_SHOW_BARS_BY_SWIPE
+ final boolean isImmersive = behavior == BEHAVIOR_SHOW_BARS_BY_SWIPE
|| behavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
- if (diff == 0
+ final boolean isFullscreen = !requestedInsets.getSourceOrDefaultVisibility(ITYPE_STATUS_BAR)
+ || !requestedInsets.getSourceOrDefaultVisibility(ITYPE_NAVIGATION_BAR);
+ if (mLastDisableFlags == disableFlags
&& mLastAppearance == appearance
&& mLastFullscreenAppearance == fullscreenAppearance
&& mLastDockedAppearance == dockedAppearance
@@ -3068,14 +2932,10 @@ public class DisplayPolicy {
&& mLastFocusIsImmersive == isImmersive
&& mLastNonDockedStackBounds.equals(mNonDockedStackBounds)
&& mLastDockedStackBounds.equals(mDockedStackBounds)) {
- return 0;
+ return false;
}
- // Obtains which types should show transient and which types should abort transient.
- // If there is no transient state change, this pair will contain two empty arrays.
- final Pair<int[], int[]> transientState = getTransientState(visibility, mLastSystemUiFlags);
-
- mLastSystemUiFlags = visibility;
+ mLastDisableFlags = disableFlags;
mLastAppearance = appearance;
mLastFullscreenAppearance = fullscreenAppearance;
mLastDockedAppearance = dockedAppearance;
@@ -3097,50 +2957,17 @@ public class DisplayPolicy {
StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
if (statusBar != null) {
final int displayId = getDisplayId();
- statusBar.setDisableFlags(displayId, visibility & StatusBarManager.DISABLE_MASK,
- cause);
- if (transientState.first.length > 0) {
- statusBar.showTransient(displayId, transientState.first);
- }
- if (transientState.second.length > 0) {
- statusBar.abortTransient(displayId, transientState.second);
- }
+ statusBar.setDisableFlags(displayId, disableFlags, cause);
statusBar.onSystemBarAppearanceChanged(displayId, appearance,
appearanceRegions, isNavbarColorManagedByIme);
statusBar.topAppWindowChanged(displayId, isFullscreen, isImmersive);
- // TODO(b/118118435): Remove this after removing system UI visibilities.
- synchronized (mLock) {
- mDisplayContent.statusBarVisibilityChanged(
- visibility & ~(View.STATUS_BAR_UNHIDE | View.NAVIGATION_BAR_UNHIDE));
- }
}
});
- return diff;
- }
-
- private static Pair<int[], int[]> getTransientState(int vis, int oldVis) {
- final IntArray typesToShow = new IntArray(0);
- final IntArray typesToAbort = new IntArray(0);
- updateTransientState(vis, oldVis, View.STATUS_BAR_TRANSIENT, ITYPE_STATUS_BAR, typesToShow,
- typesToAbort);
- updateTransientState(vis, oldVis, View.NAVIGATION_BAR_TRANSIENT,
- ITYPE_NAVIGATION_BAR, typesToShow, typesToAbort);
- return Pair.create(typesToShow.toArray(), typesToAbort.toArray());
- }
-
- private static void updateTransientState(int vis, int oldVis, int transientFlag,
- @InternalInsetsType int type, IntArray typesToShow, IntArray typesToAbort) {
- final boolean wasTransient = (oldVis & transientFlag) != 0;
- final boolean isTransient = (vis & transientFlag) != 0;
- if (!wasTransient && isTransient) {
- typesToShow.add(type);
- } else if (wasTransient && !isTransient) {
- typesToAbort.add(type);
- }
+ return true;
}
- private int updateLightStatusBarAppearanceLw(@Appearance int appearance, WindowState opaque,
+ private int updateLightStatusBarLw(@Appearance int appearance, WindowState opaque,
WindowState opaqueOrDimming) {
final boolean onKeyguard = isKeyguardShowing() && !isKeyguardOccluded();
final WindowState statusColorWin = onKeyguard ? mNotificationShade : opaqueOrDimming;
@@ -3207,24 +3034,7 @@ public class DisplayPolicy {
}
@VisibleForTesting
- static int updateLightNavigationBarLw(int vis, WindowState opaque, WindowState opaqueOrDimming,
- WindowState imeWindow, WindowState navColorWin) {
-
- if (navColorWin != null) {
- if (navColorWin == imeWindow || navColorWin == opaque) {
- // Respect the light flag.
- vis &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
- vis |= PolicyControl.getSystemUiVisibility(navColorWin, null)
- & View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
- } else if (navColorWin == opaqueOrDimming && navColorWin.isDimming()) {
- // Clear the light flag for dimming window.
- vis &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
- }
- }
- return vis;
- }
-
- private int updateLightNavigationBarAppearanceLw(int appearance, WindowState opaque,
+ int updateLightNavigationBarLw(int appearance, WindowState opaque,
WindowState opaqueOrDimming, WindowState imeWindow, WindowState navColorWin) {
if (navColorWin != null) {
@@ -3244,7 +3054,7 @@ public class DisplayPolicy {
return appearance;
}
- private Pair<Integer, WindowState> updateSystemBarsLw(WindowState win, int oldVis, int vis) {
+ private int updateSystemBarsLw(WindowState win, int disableFlags) {
final boolean dockedStackVisible = mDisplayContent.getDefaultTaskDisplayArea()
.isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
final boolean freeformStackVisible = mDisplayContent.getDefaultTaskDisplayArea()
@@ -3257,115 +3067,46 @@ public class DisplayPolicy {
mForceShowSystemBars = dockedStackVisible || win.inFreeformWindowingMode() || resizing;
final boolean forceOpaqueStatusBar = mForceShowSystemBars && !isKeyguardShowing();
- // apply translucent bar vis flags
- WindowState fullscreenTransWin = isKeyguardShowing() && !isKeyguardOccluded()
- ? mNotificationShade
- : mTopFullscreenOpaqueWindowState;
- vis = mStatusBarController.applyTranslucentFlagLw(fullscreenTransWin, vis, oldVis);
- vis = mNavigationBarController.applyTranslucentFlagLw(fullscreenTransWin, vis, oldVis);
- int dockedVis = mStatusBarController.applyTranslucentFlagLw(
- mTopDockedOpaqueWindowState, 0, 0);
- dockedVis = mNavigationBarController.applyTranslucentFlagLw(
- mTopDockedOpaqueWindowState, dockedVis, 0);
-
final boolean fullscreenDrawsStatusBarBackground =
- drawsStatusBarBackground(vis, mTopFullscreenOpaqueWindowState);
+ drawsStatusBarBackground(mTopFullscreenOpaqueWindowState);
final boolean dockedDrawsStatusBarBackground =
- drawsStatusBarBackground(dockedVis, mTopDockedOpaqueWindowState);
+ drawsStatusBarBackground(mTopDockedOpaqueWindowState);
final boolean fullscreenDrawsNavBarBackground =
- drawsNavigationBarBackground(vis, mTopFullscreenOpaqueWindowState);
+ drawsNavigationBarBackground(mTopFullscreenOpaqueWindowState);
final boolean dockedDrawsNavigationBarBackground =
- drawsNavigationBarBackground(dockedVis, mTopDockedOpaqueWindowState);
+ drawsNavigationBarBackground(mTopDockedOpaqueWindowState);
- // prevent status bar interaction from clearing certain flags
- int type = win.getAttrs().type;
- boolean notificationShadeHasFocus = type == TYPE_NOTIFICATION_SHADE;
- if (notificationShadeHasFocus && !isKeyguardShowing()) {
- int flags = View.SYSTEM_UI_FLAG_FULLSCREEN
- | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
- | View.SYSTEM_UI_FLAG_IMMERSIVE
- | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
- | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
- if (isKeyguardOccluded()) {
- flags |= View.STATUS_BAR_TRANSLUCENT | View.NAVIGATION_BAR_TRANSLUCENT;
- }
- vis = (vis & ~flags) | (oldVis & flags);
- }
+ int appearance = APPEARANCE_OPAQUE_NAVIGATION_BARS | APPEARANCE_OPAQUE_STATUS_BARS;
if (fullscreenDrawsStatusBarBackground && dockedDrawsStatusBarBackground) {
- vis |= View.STATUS_BAR_TRANSPARENT;
- vis &= ~View.STATUS_BAR_TRANSLUCENT;
- } else if (forceOpaqueStatusBar) {
- vis &= ~(View.STATUS_BAR_TRANSLUCENT | View.STATUS_BAR_TRANSPARENT);
- }
-
- vis = configureNavBarOpacity(vis, dockedStackVisible, freeformStackVisible, resizing,
- fullscreenDrawsNavBarBackground, dockedDrawsNavigationBarBackground);
-
- // update status bar
- boolean immersiveSticky =
- (vis & View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0;
- final boolean hideStatusBarWM =
- mTopFullscreenOpaqueWindowState != null
- && (PolicyControl.getWindowFlags(mTopFullscreenOpaqueWindowState, null)
- & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0;
- final boolean hideStatusBarSysui =
- (vis & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0;
- final boolean hideNavBarSysui = (vis & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0
- // We shouldn't rely on the system UI visibilities anymore because the window can
- // use the new API (e.g., WindowInsetsController.hide) to hide navigation bar.
- // TODO(b/149813814): clean up the system UI flag usages in this function.
- || !win.getRequestedInsetsState().getSourceOrDefaultVisibility(
- ITYPE_NAVIGATION_BAR);
+ appearance &= ~APPEARANCE_OPAQUE_STATUS_BARS;
+ }
- final boolean transientStatusBarAllowed = getStatusBar() != null
- && (notificationShadeHasFocus || (!mForceShowSystemBars
- && (hideStatusBarWM || (hideStatusBarSysui && immersiveSticky))));
+ appearance = configureNavBarOpacity(appearance, dockedStackVisible,
+ freeformStackVisible, resizing, fullscreenDrawsNavBarBackground,
+ dockedDrawsNavigationBarBackground);
- final boolean transientNavBarAllowed = mNavigationBar != null
- && !mForceShowSystemBars && hideNavBarSysui && immersiveSticky;
+ final InsetsState requestedInsetsState = win.getRequestedInsetsState();
+ final boolean requestHideNavBar = !requestedInsetsState.getSourceOrDefaultVisibility(
+ ITYPE_NAVIGATION_BAR);
final long now = SystemClock.uptimeMillis();
final boolean pendingPanic = mPendingPanicGestureUptime != 0
&& now - mPendingPanicGestureUptime <= PANIC_GESTURE_EXPIRATION;
final DisplayPolicy defaultDisplayPolicy =
mService.getDefaultDisplayContentLocked().getDisplayPolicy();
- if (pendingPanic && hideNavBarSysui && win != mNotificationShade
+ if (pendingPanic && requestHideNavBar && win != mNotificationShade
&& getInsetsPolicy().isHidden(ITYPE_NAVIGATION_BAR)
// TODO (b/111955725): Show keyguard presentation on all external displays
&& defaultDisplayPolicy.isKeyguardDrawComplete()) {
// The user performed the panic gesture recently, we're about to hide the bars,
// we're no longer on the Keyguard and the screen is ready. We can now request the bars.
mPendingPanicGestureUptime = 0;
- if (!isNavBarEmpty(vis)) {
+ if (!isNavBarEmpty(disableFlags)) {
mDisplayContent.getInsetsPolicy().showTransient(SHOW_TYPES_FOR_PANIC);
}
}
- final boolean denyTransientStatus = mStatusBarController.isTransientShowRequested()
- && !transientStatusBarAllowed && hideStatusBarSysui;
- final boolean denyTransientNav = mNavigationBarController.isTransientShowRequested()
- && !transientNavBarAllowed;
- if (denyTransientStatus || denyTransientNav || mForceShowSystemBars) {
- // clear the clearable flags instead
- clearClearableFlagsLw();
- vis &= ~View.SYSTEM_UI_CLEARABLE_FLAGS;
- }
-
- final boolean immersive = (vis & View.SYSTEM_UI_FLAG_IMMERSIVE) != 0;
- immersiveSticky = (vis & View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0;
- final boolean navAllowedHidden = immersive || immersiveSticky;
-
- if (hideNavBarSysui && !navAllowedHidden
- && mService.mPolicy.getWindowLayerLw(win)
- > mService.mPolicy.getWindowLayerFromTypeLw(TYPE_INPUT_CONSUMER)) {
- // We can't hide the navbar from this window otherwise the input consumer would not get
- // the input events.
- vis = (vis & ~View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
- }
-
- vis = mStatusBarController.updateVisibilityLw(transientStatusBarAllowed, oldVis, vis);
-
// update navigation bar
boolean oldImmersiveMode = mLastImmersiveMode;
boolean newImmersiveMode = isImmersiveMode(win);
@@ -3374,23 +3115,13 @@ public class DisplayPolicy {
final String pkg = win.getOwningPackage();
mImmersiveModeConfirmation.immersiveModeChangedLw(pkg, newImmersiveMode,
mService.mPolicy.isUserSetupComplete(),
- isNavBarEmpty(win.getSystemUiVisibility()));
+ isNavBarEmpty(disableFlags));
}
- vis = mNavigationBarController.updateVisibilityLw(transientNavBarAllowed, oldVis, vis);
-
- final WindowState navColorWin = chooseNavigationColorWindowLw(
- mTopFullscreenOpaqueWindowState, mTopFullscreenOpaqueOrDimmingWindowState,
- mDisplayContent.mInputMethodWindow, mNavigationBarPosition);
- vis = updateLightNavigationBarLw(vis, mTopFullscreenOpaqueWindowState,
- mTopFullscreenOpaqueOrDimmingWindowState,
- mDisplayContent.mInputMethodWindow, navColorWin);
-
- return Pair.create(vis, navColorWin);
+ return appearance;
}
- private boolean drawsBarBackground(int vis, WindowState win, BarController controller,
- int translucentFlag) {
+ private boolean drawsBarBackground(WindowState win, BarController controller) {
if (!controller.isTransparentAllowed(win)) {
return false;
}
@@ -3403,73 +3134,59 @@ public class DisplayPolicy {
final boolean forceDrawsSystemBars =
(win.getAttrs().privateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
- return forceDrawsSystemBars || drawsSystemBars && (vis & translucentFlag) == 0;
+ return forceDrawsSystemBars || drawsSystemBars;
}
- private boolean drawsStatusBarBackground(int vis, WindowState win) {
- return drawsBarBackground(vis, win, mStatusBarController, FLAG_TRANSLUCENT_STATUS);
+ private boolean drawsStatusBarBackground(WindowState win) {
+ return drawsBarBackground(win, mStatusBarController);
}
- private boolean drawsNavigationBarBackground(int vis, WindowState win) {
- return drawsBarBackground(vis, win, mNavigationBarController, FLAG_TRANSLUCENT_NAVIGATION);
+ private boolean drawsNavigationBarBackground(WindowState win) {
+ return drawsBarBackground(win, mNavigationBarController);
}
/**
* @return the current visibility flags with the nav-bar opacity related flags toggled based
* on the nav bar opacity rules chosen by {@link #mNavBarOpacityMode}.
*/
- private int configureNavBarOpacity(int visibility, boolean dockedStackVisible,
+ private int configureNavBarOpacity(int appearance, boolean dockedStackVisible,
boolean freeformStackVisible, boolean isDockedDividerResizing,
boolean fullscreenDrawsBackground, boolean dockedDrawsNavigationBarBackground) {
if (mNavBarOpacityMode == NAV_BAR_FORCE_TRANSPARENT) {
if (fullscreenDrawsBackground && dockedDrawsNavigationBarBackground) {
- visibility = setNavBarTransparentFlag(visibility);
+ appearance = clearNavBarOpaqueFlag(appearance);
} else if (dockedStackVisible) {
- visibility = setNavBarOpaqueFlag(visibility);
+ appearance = setNavBarOpaqueFlag(appearance);
}
} else if (mNavBarOpacityMode == NAV_BAR_OPAQUE_WHEN_FREEFORM_OR_DOCKED) {
if (dockedStackVisible || freeformStackVisible || isDockedDividerResizing) {
if (mIsFreeformWindowOverlappingWithNavBar) {
- visibility = setNavBarTranslucentFlag(visibility);
+ appearance = clearNavBarOpaqueFlag(appearance);
} else {
- visibility = setNavBarOpaqueFlag(visibility);
+ appearance = setNavBarOpaqueFlag(appearance);
}
} else if (fullscreenDrawsBackground) {
- visibility = setNavBarTransparentFlag(visibility);
+ appearance = clearNavBarOpaqueFlag(appearance);
}
} else if (mNavBarOpacityMode == NAV_BAR_TRANSLUCENT_WHEN_FREEFORM_OPAQUE_OTHERWISE) {
if (isDockedDividerResizing) {
- visibility = setNavBarOpaqueFlag(visibility);
+ appearance = setNavBarOpaqueFlag(appearance);
} else if (freeformStackVisible) {
- visibility = setNavBarTranslucentFlag(visibility);
+ appearance = clearNavBarOpaqueFlag(appearance);
} else {
- visibility = setNavBarOpaqueFlag(visibility);
+ appearance = setNavBarOpaqueFlag(appearance);
}
}
- return visibility;
- }
-
- private int setNavBarOpaqueFlag(int visibility) {
- return visibility & ~(View.NAVIGATION_BAR_TRANSLUCENT | View.NAVIGATION_BAR_TRANSPARENT);
- }
-
- private int setNavBarTranslucentFlag(int visibility) {
- visibility &= ~View.NAVIGATION_BAR_TRANSPARENT;
- return visibility | View.NAVIGATION_BAR_TRANSLUCENT;
+ return appearance;
}
- private int setNavBarTransparentFlag(int visibility) {
- visibility &= ~View.NAVIGATION_BAR_TRANSLUCENT;
- return visibility | View.NAVIGATION_BAR_TRANSPARENT;
+ private int setNavBarOpaqueFlag(int appearance) {
+ return appearance | APPEARANCE_OPAQUE_NAVIGATION_BARS;
}
- private void clearClearableFlagsLw() {
- int newVal = mResettingSystemUiFlags | View.SYSTEM_UI_CLEARABLE_FLAGS;
- if (newVal != mResettingSystemUiFlags) {
- mResettingSystemUiFlags = newVal;
- mDisplayContent.reevaluateStatusBarVisibility();
- }
+ private int clearNavBarOpaqueFlag(int appearance) {
+ return appearance & ~APPEARANCE_OPAQUE_NAVIGATION_BARS;
}
private boolean isImmersiveMode(WindowState win) {
@@ -3520,7 +3237,7 @@ public class DisplayPolicy {
// taken over the whole screen.
boolean panic = mImmersiveModeConfirmation.onPowerKeyDown(isScreenOn,
SystemClock.elapsedRealtime(), isImmersiveMode(mSystemUiControllingWindow),
- isNavBarEmpty(mLastSystemUiFlags));
+ isNavBarEmpty(mLastDisableFlags));
if (panic) {
mHandler.post(mHiddenNavPanic);
}
@@ -3579,14 +3296,9 @@ public class DisplayPolicy {
pw.print(prefix); pw.print("mKeyguardDrawComplete="); pw.print(mKeyguardDrawComplete);
pw.print(" mWindowManagerDrawComplete="); pw.println(mWindowManagerDrawComplete);
pw.print(prefix); pw.print("mHdmiPlugged="); pw.println(mHdmiPlugged);
- if (mLastSystemUiFlags != 0 || mResettingSystemUiFlags != 0
- || mForceClearedSystemUiFlags != 0) {
- pw.print(prefix); pw.print("mLastSystemUiFlags=0x");
- pw.print(Integer.toHexString(mLastSystemUiFlags));
- pw.print(" mResettingSystemUiFlags=0x");
- pw.print(Integer.toHexString(mResettingSystemUiFlags));
- pw.print(" mForceClearedSystemUiFlags=0x");
- pw.println(Integer.toHexString(mForceClearedSystemUiFlags));
+ if (mLastDisableFlags != 0) {
+ pw.print(prefix); pw.print("mLastDisableFlags=0x");
+ pw.print(Integer.toHexString(mLastDisableFlags));
}
pw.print(prefix); pw.print("mShowingDream="); pw.print(mShowingDream);
pw.print(" mDreamingLockscreen="); pw.print(mDreamingLockscreen);
@@ -3635,8 +3347,6 @@ public class DisplayPolicy {
pw.print(prefix); pw.print("mRemoteInsetsControllerControlsSystemBars");
pw.print(mDisplayContent.getInsetsPolicy().getRemoteInsetsControllerControlsSystemBars());
pw.print(" mAllowLockscreenWhenOn="); pw.println(mAllowLockscreenWhenOn);
- mStatusBarController.dump(pw, prefix);
- mNavigationBarController.dump(pw, prefix);
pw.print(prefix); pw.println("Looper state:");
mHandler.getLooper().dump(new PrintWriterPrinter(pw), prefix + " ");
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/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 18a25033b1e6..361788497e11 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -36,12 +36,12 @@ 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;
import android.view.SurfaceControl;
import android.view.SyncRtSurfaceTransactionApplier;
-import android.view.ViewRootImpl;
import android.view.WindowInsetsAnimation;
import android.view.WindowInsetsAnimation.Bounds;
import android.view.WindowInsetsAnimationControlListener;
@@ -139,9 +139,6 @@ class InsetsPolicy {
getFakeControlTarget(focusedWin, statusControlTarget),
navControlTarget,
getFakeControlTarget(focusedWin, navControlTarget));
- if (ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL) {
- return;
- }
mStatusBar.updateVisibility(statusControlTarget, ITYPE_STATUS_BAR);
mNavBar.updateVisibility(navControlTarget, ITYPE_NAVIGATION_BAR);
mPolicy.updateHideNavInputEventReceiver();
@@ -211,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;
}
@@ -251,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,
@@ -289,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();
@@ -297,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 e00c9e7ac38b..a7f32c09b83b 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -19,10 +19,6 @@ package com.android.server.wm;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
-import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
-import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME;
-import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
-import static android.view.ViewRootImpl.sNewInsetsMode;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_INSETS_CONTROL;
@@ -93,13 +89,14 @@ class InsetsSourceProvider {
mFakeControl = new InsetsSourceControl(source.getType(), null /* leash */,
new Point());
- final int type = source.getType();
- if (type == ITYPE_STATUS_BAR || type == ITYPE_NAVIGATION_BAR) {
- mControllable = sNewInsetsMode == NEW_INSETS_MODE_FULL;
- } else if (type == ITYPE_IME) {
- mControllable = sNewInsetsMode >= NEW_INSETS_MODE_IME;
- } else {
- mControllable = false;
+ switch (source.getType()) {
+ case ITYPE_STATUS_BAR:
+ case ITYPE_NAVIGATION_BAR:
+ case ITYPE_IME:
+ mControllable = true;
+ break;
+ default:
+ mControllable = false;
}
}
@@ -395,7 +392,7 @@ class InsetsSourceProvider {
}
boolean isClientVisible() {
- return sNewInsetsMode == NEW_INSETS_MODE_NONE || mClientVisible;
+ return mClientVisible;
}
/**
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 5520ad37e8ed..c0bdbff6b761 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -24,8 +24,6 @@ import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_INVALID;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
-import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
-import static android.view.ViewRootImpl.sNewInsetsMode;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
@@ -382,9 +380,6 @@ class InsetsStateController {
*/
void onControlFakeTargetChanged(@InternalInsetsType int type,
@Nullable InsetsControlTarget fakeTarget) {
- if (sNewInsetsMode != NEW_INSETS_MODE_FULL) {
- return;
- }
final InsetsControlTarget previous = mTypeFakeControlTargetMap.get(type);
if (fakeTarget == previous) {
return;
diff --git a/services/core/java/com/android/server/wm/StatusBarController.java b/services/core/java/com/android/server/wm/StatusBarController.java
deleted file mode 100644
index 3564e0bce5f5..000000000000
--- a/services/core/java/com/android/server/wm/StatusBarController.java
+++ /dev/null
@@ -1,109 +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 static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
-import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
-
-import static com.android.server.wm.WindowManagerInternal.AppTransitionListener;
-
-import android.app.StatusBarManager;
-import android.os.IBinder;
-import android.view.View;
-
-import com.android.server.statusbar.StatusBarManagerInternal;
-
-/**
- * Implements status bar specific behavior.
- */
-public class StatusBarController extends BarController {
-
- private final AppTransitionListener mAppTransitionListener = new AppTransitionListener() {
-
- private Runnable mAppTransitionPending = () -> {
- StatusBarManagerInternal statusBar = getStatusBarInternal();
- if (statusBar != null) {
- statusBar.appTransitionPending(mDisplayId);
- }
- };
-
- private Runnable mAppTransitionCancelled = () -> {
- StatusBarManagerInternal statusBar = getStatusBarInternal();
- if (statusBar != null) {
- statusBar.appTransitionCancelled(mDisplayId);
- }
- };
-
- private Runnable mAppTransitionFinished = () -> {
- StatusBarManagerInternal statusBar = getStatusBarInternal();
- if (statusBar != null) {
- statusBar.appTransitionFinished(mDisplayId);
- }
- };
-
- @Override
- public void onAppTransitionPendingLocked() {
- mHandler.post(mAppTransitionPending);
- }
-
- @Override
- public int onAppTransitionStartingLocked(int transit, long duration,
- long statusBarAnimationStartTime, long statusBarAnimationDuration) {
- mHandler.post(() -> {
- StatusBarManagerInternal statusBar = getStatusBarInternal();
- if (statusBar != null) {
- statusBar.appTransitionStarting(mDisplayId,
- statusBarAnimationStartTime, statusBarAnimationDuration);
- }
- });
- return 0;
- }
-
- @Override
- public void onAppTransitionCancelledLocked(int transit) {
- mHandler.post(mAppTransitionCancelled);
- }
-
- @Override
- public void onAppTransitionFinishedLocked(IBinder token) {
- mHandler.post(mAppTransitionFinished);
- }
- };
-
- StatusBarController(int displayId) {
- super("StatusBar",
- displayId,
- View.STATUS_BAR_TRANSIENT,
- View.STATUS_BAR_UNHIDE,
- View.STATUS_BAR_TRANSLUCENT,
- StatusBarManager.WINDOW_STATUS_BAR,
- TYPE_STATUS_BAR,
- FLAG_TRANSLUCENT_STATUS,
- View.STATUS_BAR_TRANSPARENT);
- }
-
- void setTopAppHidesStatusBar(boolean hidesStatusBar) {
- StatusBarManagerInternal statusBar = getStatusBarInternal();
- if (statusBar != null) {
- statusBar.setTopAppHidesStatusBar(hidesStatusBar);
- }
- }
-
- AppTransitionListener getAppTransitionListener() {
- return mAppTransitionListener;
- }
-}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index b7a5d05486c4..2a0488d6e230 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1102,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();
@@ -5340,7 +5340,8 @@ class Task extends WindowContainer<WindowContainer> {
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.
@@ -5400,11 +5401,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());
@@ -5471,7 +5473,7 @@ class Task extends WindowContainer<WindowContainer> {
if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Enqueueing pending pause: " + 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,
@@ -5995,7 +5997,8 @@ class Task extends WindowContainer<WindowContainer> {
if (mResumedActivity != null) {
if (DEBUG_STATES) Slog.d(TAG_STATES,
"resumeTopActivityLocked: Pausing " + mResumedActivity);
- pausing |= startPausingLocked(userLeaving, false /* uiSleeping */, next);
+ pausing |= startPausingLocked(userLeaving, false /* uiSleeping */, next,
+ "resumeTopActivityInnerLocked");
}
if (pausing) {
if (DEBUG_SWITCH || DEBUG_STATES) Slog.v(TAG_STATES,
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 890f56e50bac..c11624c92117 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -29,12 +29,10 @@ 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.app.WindowConfiguration.isSplitScreenWindowingMode;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
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;
@@ -645,6 +643,12 @@ final class TaskDisplayArea extends DisplayArea<Task> {
@Override
int getOrientation(int candidate) {
+ // Only allow to specify orientation if this TDA has the focus.
+ // TODO(b/155431879) Add option to never allow a TDA to specify orientation.
+ if (!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
@@ -671,21 +675,7 @@ final class TaskDisplayArea extends DisplayArea<Task> {
return SCREEN_ORIENTATION_UNSPECIFIED;
}
- final int orientation = super.getOrientation(candidate);
- if (orientation != SCREEN_ORIENTATION_UNSET
- && orientation != SCREEN_ORIENTATION_BEHIND) {
- ProtoLog.v(WM_DEBUG_ORIENTATION,
- "App is requesting an orientation, return %d for display id=%d",
- orientation, mDisplayContent.mDisplayId);
- return orientation;
- }
-
- ProtoLog.v(WM_DEBUG_ORIENTATION,
- "No app is requesting an orientation, return %d for display id=%d",
- mDisplayContent.getLastOrientation(), mDisplayContent.mDisplayId);
- // The next app has not been requested to be visible, so we keep the current orientation
- // to prevent freezing/unfreezing the display too early.
- return mDisplayContent.getLastOrientation();
+ return super.getOrientation(candidate);
}
@Override
@@ -1219,7 +1209,7 @@ final class TaskDisplayArea extends DisplayArea<Task> {
+ " mResumedActivity=" + resumedActivity);
}
someActivityPaused |= stack.startPausingLocked(userLeaving, false /* uiSleeping*/,
- resuming);
+ resuming, "pauseBackStacks");
}
}
return someActivityPaused;
@@ -1880,6 +1870,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/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index f39fa1b7fbc8..bc4f75276a95 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -481,13 +481,11 @@ 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,
- attrs.privateFlags, attrs.systemUiVisibility, task.getTaskDescription(),
+ attrs.privateFlags, attrs.insetsFlags.appearance, task.getTaskDescription(),
mHighResTaskSnapshotScale, insetsState);
final int taskWidth = task.getBounds().width();
final int taskHeight = task.getBounds().height();
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index 100f1bff12a7..cccda3a4f4b8 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -19,8 +19,8 @@ package com.android.server.wm;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.graphics.Color.WHITE;
import static android.graphics.Color.alpha;
-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.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
import static android.view.WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
@@ -75,7 +75,6 @@ import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
-import android.view.ViewRootImpl;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.window.ClientWindowFrames;
@@ -167,7 +166,7 @@ class TaskSnapshotSurface implements StartingSurface {
final TaskDescription taskDescription = new TaskDescription();
taskDescription.setBackgroundColor(WHITE);
final WindowState topFullscreenOpaqueWindow;
- final int sysUiVis;
+ final int appearance;
final int windowFlags;
final int windowPrivateFlags;
final int currentOrientation;
@@ -204,8 +203,8 @@ class TaskSnapshotSurface implements StartingSurface {
topFullscreenActivity, false /* checkOpening */);
}
- sysUiVis = topFullscreenOpaqueWindow.getSystemUiVisibility();
WindowManager.LayoutParams attrs = topFullscreenOpaqueWindow.mAttrs;
+ appearance = attrs.insetsFlags.appearance;
windowFlags = attrs.flags;
windowPrivateFlags = attrs.privateFlags;
@@ -221,11 +220,8 @@ class TaskSnapshotSurface implements StartingSurface {
layoutParams.token = activity.token;
layoutParams.width = LayoutParams.MATCH_PARENT;
layoutParams.height = LayoutParams.MATCH_PARENT;
- layoutParams.systemUiVisibility = sysUiVis;
- layoutParams.insetsFlags.behavior
- = topFullscreenOpaqueWindow.mAttrs.insetsFlags.behavior;
- layoutParams.insetsFlags.appearance
- = topFullscreenOpaqueWindow.mAttrs.insetsFlags.appearance;
+ layoutParams.insetsFlags.appearance = appearance;
+ layoutParams.insetsFlags.behavior = attrs.insetsFlags.behavior;
layoutParams.layoutInDisplayCutoutMode = attrs.layoutInDisplayCutoutMode;
layoutParams.setFitInsetsTypes(attrs.getFitInsetsTypes());
layoutParams.setFitInsetsSides(attrs.getFitInsetsSides());
@@ -241,11 +237,7 @@ 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 {
@@ -261,7 +253,7 @@ class TaskSnapshotSurface implements StartingSurface {
// Local call.
}
final TaskSnapshotSurface snapshotSurface = new TaskSnapshotSurface(service, window,
- surfaceControl, snapshot, layoutParams.getTitle(), taskDescription, sysUiVis,
+ surfaceControl, snapshot, layoutParams.getTitle(), taskDescription, appearance,
windowFlags, windowPrivateFlags, taskBounds, currentOrientation, activityType,
insetsState);
window.setOuter(snapshotSurface);
@@ -282,7 +274,7 @@ class TaskSnapshotSurface implements StartingSurface {
@VisibleForTesting
TaskSnapshotSurface(WindowManagerService service, Window window, SurfaceControl surfaceControl,
TaskSnapshot snapshot, CharSequence title, TaskDescription taskDescription,
- int sysUiVis, int windowFlags, int windowPrivateFlags, Rect taskBounds,
+ int appearance, int windowFlags, int windowPrivateFlags, Rect taskBounds,
int currentOrientation, int activityType, InsetsState insetsState) {
mService = service;
mSurface = service.mSurfaceFactory.get();
@@ -296,7 +288,7 @@ class TaskSnapshotSurface implements StartingSurface {
mBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE);
mTaskBounds = taskBounds;
mSystemBarBackgroundPainter = new SystemBarBackgroundPainter(windowFlags,
- windowPrivateFlags, sysUiVis, taskDescription, 1f, insetsState);
+ windowPrivateFlags, appearance, taskDescription, 1f, insetsState);
mStatusBarColor = taskDescription.getStatusBarColor();
mOrientationOnCreation = currentOrientation;
mActivityType = activityType;
@@ -554,28 +546,26 @@ class TaskSnapshotSurface implements StartingSurface {
private final int mNavigationBarColor;
private final int mWindowFlags;
private final int mWindowPrivateFlags;
- private final int mSysUiVis;
private final float mScale;
private final InsetsState mInsetsState;
private final Rect mSystemBarInsets = new Rect();
- SystemBarBackgroundPainter(int windowFlags, int windowPrivateFlags, int sysUiVis,
+ SystemBarBackgroundPainter(int windowFlags, int windowPrivateFlags, int appearance,
TaskDescription taskDescription, float scale, InsetsState insetsState) {
mWindowFlags = windowFlags;
mWindowPrivateFlags = windowPrivateFlags;
- mSysUiVis = sysUiVis;
mScale = scale;
final Context context = ActivityThread.currentActivityThread().getSystemUiContext();
final int semiTransparent = context.getColor(
R.color.system_bar_background_semi_transparent);
mStatusBarColor = DecorView.calculateBarColor(windowFlags, FLAG_TRANSLUCENT_STATUS,
- semiTransparent, taskDescription.getStatusBarColor(), sysUiVis,
- SYSTEM_UI_FLAG_LIGHT_STATUS_BAR,
+ semiTransparent, taskDescription.getStatusBarColor(), appearance,
+ APPEARANCE_LIGHT_STATUS_BARS,
taskDescription.getEnsureStatusBarContrastWhenTransparent());
mNavigationBarColor = DecorView.calculateBarColor(windowFlags,
FLAG_TRANSLUCENT_NAVIGATION, semiTransparent,
- taskDescription.getNavigationBarColor(), sysUiVis,
- SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
+ taskDescription.getNavigationBarColor(), appearance,
+ APPEARANCE_LIGHT_NAVIGATION_BARS,
taskDescription.getEnsureNavigationBarContrastWhenTransparent()
&& context.getResources().getBoolean(R.bool.config_navBarNeedsScrim));
mStatusBarPaint.setColor(mStatusBarColor);
@@ -590,11 +580,8 @@ class TaskSnapshotSurface implements StartingSurface {
int getStatusBarColorViewHeight() {
final boolean forceBarBackground =
(mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
- if (ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL
- ? STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
- mSysUiVis, mStatusBarColor, mWindowFlags, forceBarBackground)
- : STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
- mInsetsState, mStatusBarColor, mWindowFlags, forceBarBackground)) {
+ if (STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
+ mInsetsState, mStatusBarColor, mWindowFlags, forceBarBackground)) {
return (int) (mSystemBarInsets.top * mScale);
} else {
return 0;
@@ -604,11 +591,8 @@ class TaskSnapshotSurface implements StartingSurface {
private boolean isNavigationBarColorViewVisible() {
final boolean forceBarBackground =
(mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
- return ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL
- ? NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
- mSysUiVis, mNavigationBarColor, mWindowFlags, forceBarBackground)
- : NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
- mInsetsState, mNavigationBarColor, mWindowFlags, forceBarBackground);
+ return NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
+ mInsetsState, mNavigationBarColor, mWindowFlags, forceBarBackground);
}
void drawDecors(Canvas c, @Nullable Rect alreadyDrawnFrame) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3a2b67085e35..8c2619d75201 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3069,6 +3069,9 @@ public class WindowManagerService extends IWindowManager.Stub
if (!checkCallingPermission(permission.CONTROL_KEYGUARD, "dismissKeyguard")) {
throw new SecurityException("Requires CONTROL_KEYGUARD permission");
}
+ if (mAtmInternal.isDreaming()) {
+ mAtmService.mStackSupervisor.wakeUp("dismissKeyguard");
+ }
synchronized (mGlobalLock) {
mPolicy.dismissKeyguardLw(callback, message);
}
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 8bf0820c7dad..67d230aae0fb 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -768,7 +768,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;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f2f3cefae8a5..e4a079d84a2a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1531,6 +1531,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);
}
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index 95d4ba7c199a..678308af34ea 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -36,6 +36,9 @@
using android::base::StringPrintf;
using android::base::WriteStringToFile;
+#define SYNC_RECEIVED_WHILE_FROZEN (1)
+#define ASYNC_RECEIVED_WHILE_FROZEN (2)
+
namespace android {
// This performs per-process reclaim on all processes belonging to non-app UIDs.
@@ -99,12 +102,37 @@ static void com_android_server_am_CachedAppOptimizer_freezeBinder(
}
}
+static jint com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo(JNIEnv *env,
+ jobject clazz, jint pid) {
+ bool syncReceived = false, asyncReceived = false;
+
+ int error = IPCThreadState::getProcessFreezeInfo(pid, &syncReceived, &asyncReceived);
+
+ if (error < 0) {
+ jniThrowException(env, "java/lang/RuntimeException", strerror(error));
+ }
+
+ jint retVal = 0;
+
+ if(syncReceived) {
+ retVal |= SYNC_RECEIVED_WHILE_FROZEN;;
+ }
+
+ if(asyncReceived) {
+ retVal |= ASYNC_RECEIVED_WHILE_FROZEN;
+ }
+
+ return retVal;
+}
+
static const JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
{"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem},
{"enableFreezerInternal", "(Z)V",
(void*)com_android_server_am_CachedAppOptimizer_enableFreezerInternal},
- {"freezeBinder", "(IZ)V", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder}
+ {"freezeBinder", "(IZ)V", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
+ {"getBinderFreezeInfo", "(I)I",
+ (void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo}
};
int register_android_server_am_CachedAppOptimizer(JNIEnv* env)
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/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f8d457e877fa..183a1495b075 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2081,9 +2081,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
ActiveAdmin getActiveAdminUncheckedLocked(ComponentName who, int userHandle, boolean parent) {
ensureLocked();
if (parent) {
- Preconditions.checkCallAuthorization(isManagedProfile(userHandle), String.format(
- "You can not call APIs on the parent profile outside a managed profile, "
- + "userId = %d", userHandle));
+ enforceManagedProfile(userHandle, "call APIs on the parent profile");
}
ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle);
if (admin != null && parent) {
@@ -2259,7 +2257,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Nullable String permission) throws SecurityException {
ensureLocked();
if (parent) {
- Preconditions.checkCallingUser(isManagedProfile(getCallerIdentity(who).getUserId()));
+ enforceManagedProfile(mInjector.userHandleGetCallingUserId(),
+ "call APIs on the parent profile");
}
ActiveAdmin admin = getActiveAdminOrCheckPermissionForCallerLocked(
who, reqPolicy, permission);
@@ -2853,7 +2852,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!mHasFeature) {
return;
}
- Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
+ enforceManageUsers();
synchronized (getLockObject()) {
final DevicePolicyData policy = getUserData(userHandle.getIdentifier());
@@ -4043,13 +4042,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!mHasFeature) {
return true;
}
- 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());
+ final int userId = mInjector.userHandleGetCallingUserId();
+ enforceProfileOrDeviceOwner(admin);
+ enforceManagedProfile(userId, "query unified challenge status");
+ return !isSeparateProfileChallengeEnabled(userId);
}
@Override
@@ -4061,9 +4057,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
- Preconditions.checkCallAuthorization(isManagedProfile(userHandle), String.format(
- "can not call APIs refering to the parent profile outside a managed profile, "
- + "userId = %d", userHandle));
+ enforceManagedProfile(userHandle, "call APIs refering to the parent profile");
synchronized (getLockObject()) {
final int targetUser = getProfileParentId(userHandle);
@@ -4085,9 +4079,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
- Preconditions.checkCallAuthorization(!isManagedProfile(userHandle), String.format(
- "You can not check password sufficiency for a managed profile, userId = %d",
- userHandle));
+ enforceNotManagedProfile(userHandle, "check password sufficiency");
enforceUserUnlocked(userHandle);
synchronized (getLockObject()) {
@@ -4643,9 +4635,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!mHasFeature && !hasCallingPermission(permission.LOCK_DEVICE)) {
return;
}
- final CallerIdentity caller = getCallerIdentity();
- final int callingUserId = caller.getUserId();
+ final int callingUserId = mInjector.userHandleGetCallingUserId();
ComponentName adminComponent = null;
synchronized (getLockObject()) {
// Make sure the caller has any active admin with the right policy or
@@ -4662,13 +4653,16 @@ 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
- Preconditions.checkCallingUser(isManagedProfile(callingUserId));
- Preconditions.checkArgument(!parent,
- "Cannot set FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY for the parent");
+ enforceManagedProfile(
+ callingUserId, "set FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY");
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");
@@ -4713,19 +4707,32 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public void enforceCanManageCaCerts(ComponentName who, String callerPackage) {
- final CallerIdentity caller = getCallerIdentity(who, callerPackage);
- Preconditions.checkCallAuthorization(canManageCaCerts(caller));
+ 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);
+ }
}
- private boolean canManageCaCerts(CallerIdentity caller) {
- return isDeviceOwner(caller) || isProfileOwner(caller) || isCallerDelegate(caller,
- DELEGATION_CERT_INSTALL) || hasCallingOrSelfPermission(MANAGE_CA_CERTIFICATES);
+ private void enforceNetworkStackOrProfileOrDeviceOwner(ComponentName who) {
+ if (hasCallingPermission(PERMISSION_MAINLINE_NETWORK_STACK)) {
+ return;
+ }
+ enforceProfileOrDeviceOwner(who);
}
@Override
public boolean approveCaCert(String alias, int userId, boolean approval) {
- Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
-
+ enforceManageUsers();
synchronized (getLockObject()) {
Set<String> certs = getUserData(userId).mAcceptedCaCertificates;
boolean changed = (approval ? certs.add(alias) : certs.remove(alias));
@@ -4740,8 +4747,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public boolean isCaCertApproved(String alias, int userId) {
- Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
-
+ enforceManageUsers();
synchronized (getLockObject()) {
return getUserData(userId).mAcceptedCaCertificates.contains(alias);
}
@@ -4766,20 +4772,21 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
@Override
- public boolean installCaCert(ComponentName admin, String callerPackage, byte[] certBuffer) {
+ public boolean installCaCert(ComponentName admin, String callerPackage, byte[] certBuffer)
+ throws RemoteException {
if (!mHasFeature) {
return false;
}
- final CallerIdentity caller = getCallerIdentity(admin, callerPackage);
- Preconditions.checkCallAuthorization(canManageCaCerts(caller));
+ enforceCanManageCaCerts(admin, callerPackage);
+ final UserHandle userHandle = mInjector.binderGetCallingUserHandle();
final String alias = mInjector.binderWithCleanCallingIdentity(() -> {
- String installedAlias = mCertificateMonitor.installCaCert(
- caller.getUserHandle(), certBuffer);
+ String installedAlias = mCertificateMonitor.installCaCert(userHandle, certBuffer);
+ final boolean isDelegate = (admin == null);
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.INSTALL_CA_CERT)
- .setAdmin(caller.getPackageName())
- .setBoolean(/* isDelegate */ admin == null)
+ .setAdmin(callerPackage)
+ .setBoolean(isDelegate)
.write();
return installedAlias;
});
@@ -4790,8 +4797,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
synchronized (getLockObject()) {
- getUserData(caller.getUserId()).mOwnerInstalledCaCerts.add(alias);
- saveSettingsLocked(caller.getUserId());
+ getUserData(userHandle.getIdentifier()).mOwnerInstalledCaCerts.add(alias);
+ saveSettingsLocked(userHandle.getIdentifier());
}
return true;
}
@@ -4801,22 +4808,22 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!mHasFeature) {
return;
}
- final CallerIdentity caller = getCallerIdentity(admin, callerPackage);
- Preconditions.checkCallAuthorization(canManageCaCerts(caller));
+ enforceCanManageCaCerts(admin, callerPackage);
+ final int userId = mInjector.userHandleGetCallingUserId();
mInjector.binderWithCleanCallingIdentity(() -> {
- mCertificateMonitor.uninstallCaCerts(caller.getUserHandle(), aliases);
+ mCertificateMonitor.uninstallCaCerts(UserHandle.of(userId), aliases);
+ final boolean isDelegate = (admin == null);
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.UNINSTALL_CA_CERTS)
- .setAdmin(caller.getPackageName())
- .setBoolean(/* isDelegate */ admin == null)
+ .setAdmin(callerPackage)
+ .setBoolean(isDelegate)
.write();
});
synchronized (getLockObject()) {
- if (getUserData(caller.getUserId()).mOwnerInstalledCaCerts.removeAll(
- Arrays.asList(aliases))) {
- saveSettingsLocked(caller.getUserId());
+ if (getUserData(userId).mOwnerInstalledCaCerts.removeAll(Arrays.asList(aliases))) {
+ saveSettingsLocked(userId);
}
}
}
@@ -5606,10 +5613,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
public boolean setAlwaysOnVpnPackage(ComponentName who, String vpnPackage, boolean lockdown,
List<String> lockdownWhitelist)
throws SecurityException {
- Objects.requireNonNull(who, "ComponentName is null");
-
+ enforceProfileOrDeviceOwner(who);
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
final int userId = caller.getUserId();
mInjector.binderWithCleanCallingIdentity(() -> {
@@ -5635,7 +5640,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_ALWAYS_ON_VPN_PACKAGE)
- .setAdmin(caller.getComponentName())
+ .setAdmin(who)
.setStrings(vpnPackage)
.setBoolean(lockdown)
.setInt(lockdownWhitelist != null ? lockdownWhitelist.size() : 0)
@@ -5655,14 +5660,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public String getAlwaysOnVpnPackage(ComponentName admin) throws SecurityException {
- Objects.requireNonNull(admin, "ComponentName is null");
-
- final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ enforceProfileOrDeviceOwner(admin);
+ final int userId = mInjector.userHandleGetCallingUserId();
return mInjector.binderWithCleanCallingIdentity(
- () -> mInjector.getConnectivityManager().getAlwaysOnVpnPackageForUser(
- caller.getUserId()));
+ () -> mInjector.getConnectivityManager().getAlwaysOnVpnPackageForUser(userId));
}
@Override
@@ -5676,14 +5678,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public boolean isAlwaysOnVpnLockdownEnabled(ComponentName admin) throws SecurityException {
- Objects.requireNonNull(admin, "ComponentName is null");
-
- final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)
- || hasCallingPermission(PERMISSION_MAINLINE_NETWORK_STACK));
+ enforceNetworkStackOrProfileOrDeviceOwner(admin);
+ final int userId = mInjector.userHandleGetCallingUserId();
return mInjector.binderWithCleanCallingIdentity(
- () -> mInjector.getConnectivityManager().isVpnLockdownEnabled(caller.getUserId()));
+ () -> mInjector.getConnectivityManager().isVpnLockdownEnabled(userId));
}
@Override
@@ -5698,14 +5697,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public List<String> getAlwaysOnVpnLockdownWhitelist(ComponentName admin)
throws SecurityException {
- Objects.requireNonNull(admin, "ComponentName is null");
-
- final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ enforceProfileOrDeviceOwner(admin);
+ final int userId = mInjector.userHandleGetCallingUserId();
return mInjector.binderWithCleanCallingIdentity(
- () -> mInjector.getConnectivityManager().getVpnLockdownWhitelist(
- caller.getUserId()));
+ () -> mInjector.getConnectivityManager().getVpnLockdownWhitelist(userId));
}
private void forceWipeDeviceNoLock(boolean wipeExtRequested, String reason, boolean wipeEuicc) {
@@ -5994,13 +5990,11 @@ 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)) {
- Preconditions.checkCallAuthorization(!isManagedProfile(userId), String.format("You can "
- + "not set the active password for a managed profile, userId = %d", userId));
+ enforceNotManagedProfile(userId, "set the active password");
}
DevicePolicyData policy = getUserData(userId);
@@ -6053,9 +6047,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(BIND_DEVICE_ADMIN));
if (!isSeparateProfileChallengeEnabled(userHandle)) {
- 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));
+ enforceNotManagedProfile(userHandle,
+ "report failed password attempt if separate profile challenge is not in place");
}
boolean wipeData = false;
@@ -7284,7 +7277,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return null;
}
if (!callingUserOnly) {
- Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
+ enforceManageUsers();
}
synchronized (getLockObject()) {
if (!mOwners.hasDeviceOwner()) {
@@ -7303,8 +7296,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!mHasFeature) {
return UserHandle.USER_NULL;
}
- Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
-
+ enforceManageUsers();
synchronized (getLockObject()) {
return mOwners.hasDeviceOwner() ? mOwners.getDeviceOwnerUserId() : UserHandle.USER_NULL;
}
@@ -7319,8 +7311,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!mHasFeature) {
return null;
}
- Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
-
+ enforceManageUsers();
synchronized (getLockObject()) {
if (!mOwners.hasDeviceOwner()) {
return null;
@@ -7545,10 +7536,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
Objects.requireNonNull(who, "ComponentName is null");
- final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallingUser(!isManagedProfile(caller.getUserId()));
-
- final int userId = caller.getUserId();
+ final int userId = mInjector.userHandleGetCallingUserId();
+ enforceNotManagedProfile(userId, "clear profile owner");
enforceUserUnlocked(userId);
synchronized (getLockObject()) {
// Check if this is the profile owner who is calling
@@ -7665,10 +7654,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!mHasFeature) {
return DevicePolicyManager.STATE_USER_UNMANAGED;
}
- final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(canManageUsers(caller));
-
- return getUserProvisioningState(caller.getUserId());
+ enforceManageUsers();
+ int userHandle = mInjector.userHandleGetCallingUserId();
+ return getUserProvisioningState(userHandle);
}
private int getUserProvisioningState(int userHandle) {
@@ -7706,8 +7694,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
transitionCheckNeeded = false;
} else {
- Preconditions.checkCallAuthorization(
- hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+ // For all other cases, caller must have MANAGE_PROFILE_AND_DEVICE_OWNERS.
+ enforceCanManageProfileAndDeviceOwners();
}
final DevicePolicyData policyData = getUserData(userHandle);
@@ -7762,13 +7750,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return;
}
Objects.requireNonNull(who, "ComponentName is null");
-
- final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller));
- Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
-
synchronized (getLockObject()) {
- final int userId = caller.getUserId();
+ // 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()) {
@@ -7794,15 +7780,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public void setProfileName(ComponentName who, String profileName) {
Objects.requireNonNull(who, "ComponentName is null");
+ enforceProfileOrDeviceOwner(who);
- final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
-
+ final int userId = UserHandle.getCallingUserId();
mInjector.binderWithCleanCallingIdentity(() -> {
- mUserManager.setUserName(caller.getUserId(), profileName);
+ mUserManager.setUserName(userId, profileName);
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PROFILE_NAME)
- .setAdmin(caller.getComponentName())
+ .setAdmin(who)
.write();
});
}
@@ -7911,8 +7896,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!mHasFeature) {
return null;
}
- Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
-
+ enforceManageUsers();
ComponentName profileOwner = getProfileOwner(userHandle);
if (profileOwner == null) {
return null;
@@ -8084,8 +8068,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
return;
}
- Preconditions.checkCallAuthorization(
- hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+ enforceCanManageProfileAndDeviceOwners();
if ((mIsWatch || hasUserSetupCompleted(userHandle))) {
if (!isCallerWithSystemUid()) {
@@ -8121,8 +8104,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@UserIdInt int userId,
boolean hasIncompatibleAccountsOrNonAdb) {
if (!isAdb()) {
- Preconditions.checkCallAuthorization(
- hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+ enforceCanManageProfileAndDeviceOwners();
}
final int code = checkDeviceOwnerProvisioningPreConditionLocked(
@@ -8176,9 +8158,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
}
- private boolean canManageUsers(CallerIdentity caller) {
- return isSystemUid(caller) || isRootUid(caller)
- || hasCallingOrSelfPermission(permission.MANAGE_USERS);
+ private void enforceManageUsers() {
+ final int callingUid = mInjector.binderGetCallingUid();
+ if (!(isCallerWithSystemUid() || callingUid == Process.ROOT_UID)) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null);
+ }
}
private boolean hasCallingPermission(String permission) {
@@ -8208,6 +8192,20 @@ 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,
@@ -8215,7 +8213,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return;
}
}
- Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
+ enforceManageUsers();
}
private void enforceProfileOwnerOrSystemUser() {
@@ -8816,7 +8814,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return null;
}
Objects.requireNonNull(who, "ComponentName is null");
-
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
@@ -8831,8 +8828,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!mHasFeature) {
return null;
}
- Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
-
+ enforceManageUsers();
synchronized (getLockObject()) {
List<String> result = null;
// If we have multiple profiles we return the intersection of the
@@ -8917,7 +8913,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return false;
}
Objects.requireNonNull(who, "ComponentName is null");
-
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
@@ -8959,7 +8954,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return null;
}
Objects.requireNonNull(who, "ComponentName is null");
-
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
@@ -8971,13 +8965,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public List getPermittedInputMethodsForCurrentUser() {
- final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(canManageUsers(caller));
+ enforceManageUsers();
+ final int callingUserId = mInjector.userHandleGetCallingUserId();
synchronized (getLockObject()) {
List<String> result = null;
// Only device or profile owners can have permitted lists set.
- DevicePolicyData policy = getUserDataUnchecked(caller.getUserId());
+ DevicePolicyData policy = getUserDataUnchecked(callingUserId);
for (int i = 0; i < policy.mAdminList.size(); i++) {
ActiveAdmin admin = policy.mAdminList.get(i);
List<String> fromAdmin = admin.permittedInputMethods;
@@ -8992,8 +8986,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(caller.getUserId());
+ List<InputMethodInfo> imes =
+ InputMethodManagerInternal.get().getInputMethodListAsUser(callingUserId);
if (imes != null) {
for (InputMethodInfo ime : imes) {
ServiceInfo serviceInfo = ime.getServiceInfo();
@@ -9437,12 +9431,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public boolean isEphemeralUser(ComponentName who) {
Objects.requireNonNull(who, "ComponentName is null");
+ enforceProfileOrDeviceOwner(who);
- final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
-
+ final int callingUserId = mInjector.userHandleGetCallingUserId();
return mInjector.binderWithCleanCallingIdentity(
- () -> mInjector.getUserManager().isUserEphemeral(caller.getUserId()));
+ () -> mInjector.getUserManager().isUserEphemeral(callingUserId));
}
@Override
@@ -10170,7 +10163,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return;
}
Objects.requireNonNull(who, "ComponentName is null");
-
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
@@ -10194,7 +10186,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return false;
}
Objects.requireNonNull(who, "ComponentName is null");
-
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
@@ -10217,23 +10208,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public void setSecondaryLockscreenEnabled(ComponentName who, boolean enabled) {
- 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()));
-
+ enforceCanSetSecondaryLockscreenEnabled(who);
synchronized (getLockObject()) {
- DevicePolicyData policy = getUserData(caller.getUserId());
+ final int userId = mInjector.userHandleGetCallingUserId();
+ DevicePolicyData policy = getUserData(userId);
policy.mSecondaryLockscreenEnabled = enabled;
- saveSettingsLocked(caller.getUserId());
+ saveSettingsLocked(userId);
}
}
@@ -10244,14 +10224,31 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
}
- private boolean isDefaultSupervisor(CallerIdentity caller) {
+ 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.
final String supervisor = mContext.getResources().getString(
com.android.internal.R.string.config_defaultSupervisionProfileOwnerComponent);
if (supervisor == null) {
- return false;
+ throw new SecurityException("Unable to set secondary lockscreen setting, no "
+ + "default supervision component defined");
}
final ComponentName supervisorComponent = ComponentName.unflattenFromString(supervisor);
- return caller.getComponentName().equals(supervisorComponent);
+ if (!who.equals(supervisorComponent)) {
+ throw new SecurityException(
+ "Admin " + who + " is not the default supervision component");
+ }
}
@Override
@@ -11591,9 +11588,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public SystemUpdateInfo getPendingSystemUpdate(ComponentName admin) {
Objects.requireNonNull(admin, "ComponentName is null");
-
- final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ enforceProfileOrDeviceOwner(admin);
return mOwners.getSystemUpdateInfo();
}
@@ -11784,11 +11779,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public int checkProvisioningPreCondition(String action, String packageName) {
- Objects.requireNonNull(packageName, "packageName is null");
-
- Preconditions.checkCallAuthorization(
- hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
-
+ Objects.requireNonNull(packageName);
+ enforceCanManageProfileAndDeviceOwners();
return checkProvisioningPreConditionSkipPermission(action, packageName);
}
@@ -12049,12 +12041,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public boolean isManagedProfile(ComponentName admin) {
- Objects.requireNonNull(admin, "ComponentName is null");
-
- final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
-
- return isManagedProfile(caller.getUserId());
+ enforceProfileOrDeviceOwner(admin);
+ return isManagedProfile(mInjector.userHandleGetCallingUserId());
}
@Override
@@ -12180,10 +12168,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return;
}
Objects.requireNonNull(who, "ComponentName is null");
-
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
-
+ enforceManagedProfile(caller.getUserId(), "set organization color");
synchronized (getLockObject()) {
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
admin.organizationColor = color;
@@ -12191,7 +12177,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_ORGANIZATION_COLOR)
- .setAdmin(caller.getComponentName())
+ .setAdmin(who)
.write();
}
@@ -12204,10 +12190,9 @@ 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;
@@ -12221,10 +12206,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return ActiveAdmin.DEF_ORGANIZATION_COLOR;
}
Objects.requireNonNull(who, "ComponentName is null");
-
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
-
+ enforceManagedProfile(caller.getUserId(), "get organization color");
synchronized (getLockObject()) {
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
return admin.organizationColor;
@@ -12240,9 +12223,8 @@ 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)
@@ -12275,10 +12257,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return null;
}
Objects.requireNonNull(who, "ComponentName is null");
-
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
-
+ enforceManagedProfile(caller.getUserId(), "get organization name");
synchronized (getLockObject()) {
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
return admin.organizationName;
@@ -12306,10 +12286,8 @@ 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)
@@ -12744,6 +12722,16 @@ 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);
@@ -12754,11 +12742,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public boolean isUninstallInQueue(final String packageName) {
- final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(
- hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS));
-
- Pair<String, Integer> packageUserPair = new Pair<>(packageName, caller.getUserId());
+ enforceCanManageDeviceAdmin();
+ final int userId = mInjector.userHandleGetCallingUserId();
+ Pair<String, Integer> packageUserPair = new Pair<>(packageName, userId);
synchronized (getLockObject()) {
return mPackagesToRemove.contains(packageUserPair);
}
@@ -12766,13 +12752,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public void uninstallPackageWithActiveAdmins(final String packageName) {
+ enforceCanManageDeviceAdmin();
Preconditions.checkArgument(!TextUtils.isEmpty(packageName));
- final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(
- hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS));
+ final int userId = mInjector.userHandleGetCallingUserId();
- final int userId = caller.getUserId();
enforceUserUnlocked(userId);
final ComponentName profileOwner = getProfileOwner(userId);
@@ -12821,9 +12805,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public boolean isDeviceProvisioned() {
- final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(canManageUsers(caller));
-
+ enforceManageUsers();
synchronized (getLockObject()) {
return getUserDataUnchecked(UserHandle.USER_SYSTEM).mUserSetupComplete;
}
@@ -12907,8 +12889,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public void setDeviceProvisioningConfigApplied() {
- Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
-
+ enforceManageUsers();
synchronized (getLockObject()) {
DevicePolicyData policy = getUserData(UserHandle.USER_SYSTEM);
policy.mDeviceProvisioningConfigApplied = true;
@@ -12918,8 +12899,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public boolean isDeviceProvisioningConfigApplied() {
- Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
-
+ enforceManageUsers();
synchronized (getLockObject()) {
final DevicePolicyData policy = getUserData(UserHandle.USER_SYSTEM);
return policy.mDeviceProvisioningConfigApplied;
@@ -12935,10 +12915,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
*/
@Override
public void forceUpdateUserSetupComplete() {
- Preconditions.checkCallAuthorization(
- hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+ enforceCanManageProfileAndDeviceOwners();
enforceCallerSystemUserHandle();
-
// no effect if it's called from user build
if (!mInjector.isBuildDebuggable()) {
return;
@@ -12959,28 +12937,25 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!mHasFeature) {
return;
}
- Objects.requireNonNull(admin, "ComponentName is null");
-
- final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
-
- toggleBackupServiceActive(caller.getUserId(), enabled);
+ Objects.requireNonNull(admin);
+ enforceProfileOrDeviceOwner(admin);
+ int userId = mInjector.userHandleGetCallingUserId();
+ toggleBackupServiceActive(userId, 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(caller.getUserId());
+ return ibm != null && ibm.isBackupServiceActive(
+ mInjector.userHandleGetCallingUserId());
} catch (RemoteException e) {
throw new IllegalStateException("Failed requesting backup service state.", e);
}
@@ -13292,9 +13267,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(
+ isDeviceOwner(caller) || isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)
+ || hasCallingOrSelfPermission(permission.MANAGE_USERS));
synchronized (getLockObject()) {
return isNetworkLoggingEnabledInternalLocked();
@@ -13562,14 +13537,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Objects.requireNonNull(admin, "ComponentName is null");
Objects.requireNonNull(packageName, "packageName is null");
Objects.requireNonNull(callback, "callback is null");
-
- final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ enforceProfileOrDeviceOwner(admin);
+ final int userId = UserHandle.getCallingUserId();
long ident = mInjector.binderClearCallingIdentity();
try {
ActivityManager.getService().clearApplicationUserData(packageName, false, callback,
- caller.getUserId());
+ userId);
} catch(RemoteException re) {
// Same process, should not happen.
} catch (SecurityException se) {
@@ -13622,9 +13596,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public List<String> getDisallowedSystemApps(ComponentName admin, int userId,
String provisioningAction) throws RemoteException {
- Preconditions.checkCallAuthorization(
- hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
-
+ enforceCanManageProfileAndDeviceOwners();
return new ArrayList<>(
mOverlayPackagesProvider.getNonRequiredApps(admin, userId, provisioningAction));
}
@@ -13635,23 +13607,31 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!mHasFeature) {
return;
}
- Objects.requireNonNull(admin, "ComponentName is null");
+
+ Objects.requireNonNull(admin, "Admin cannot be 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.");
- final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ enforceProfileOrDeviceOwner(admin);
- final int callingUserId = caller.getUserId();
+ 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 int callingUserId = mInjector.userHandleGetCallingUserId();
final DevicePolicyData policy = getUserData(callingUserId);
final DeviceAdminInfo incomingDeviceInfo = findAdmin(target, callingUserId,
/* throwForMissingPermission= */ true);
checkActiveAdminPrecondition(target, incomingDeviceInfo, policy);
- Preconditions.checkArgument(incomingDeviceInfo.supportsTransferOwnership(),
- "Provided target does not support ownership transfer.");
+ if (!incomingDeviceInfo.supportsTransferOwnership()) {
+ throw new IllegalArgumentException("Provided target does not support "
+ + "ownership transfer.");
+ }
final long id = mInjector.binderClearCallingIdentity();
String ownerType = null;
@@ -13674,7 +13654,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (bundle == null) {
bundle = new PersistableBundle();
}
- if (isProfileOwner(caller)) {
+ if (isProfileOwner(admin, callingUserId)) {
ownerType = ADMIN_TYPE_PROFILE_OWNER;
prepareTransfer(admin, target, bundle, callingUserId,
ADMIN_TYPE_PROFILE_OWNER);
@@ -13685,7 +13665,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (isUserAffiliatedWithDeviceLocked(callingUserId)) {
notifyAffiliatedProfileTransferOwnershipComplete(callingUserId);
}
- } else if (isDeviceOwner(caller)) {
+ } else if (isDeviceOwner(admin, callingUserId)) {
ownerType = ADMIN_TYPE_DEVICE_OWNER;
prepareTransfer(admin, target, bundle, callingUserId,
ADMIN_TYPE_DEVICE_OWNER);
@@ -14355,8 +14335,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!mHasFeature) {
return false;
}
- Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
-
+ enforceManageUsers();
long id = mInjector.binderClearCallingIdentity();
try {
return isManagedKioskInternal();
@@ -14381,8 +14360,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!mHasFeature) {
return false;
}
- Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
-
+ enforceManageUsers();
return mInjector.binderWithCleanCallingIdentity(() -> isUnattendedManagedKioskUnchecked());
}
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 10a508b06086..44a07a17824f 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -859,7 +859,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()) {
@@ -867,11 +867,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;
@@ -1584,67 +1588,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);
}
}
@@ -1677,6 +1652,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);
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index a49e0f36c9bc..d820417e73ed 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);
@@ -349,13 +349,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 ddd23778884a..eca9f1556d43 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -293,6 +293,8 @@ public final class SystemServer {
"com.android.server.timedetector.TimeDetectorService$Lifecycle";
private static final String TIME_ZONE_DETECTOR_SERVICE_CLASS =
"com.android.server.timezonedetector.TimeZoneDetectorService$Lifecycle";
+ private static final String LOCATION_TIME_ZONE_MANAGER_SERVICE_CLASS =
+ "com.android.server.location.timezone.LocationTimeZoneManagerService$Lifecycle";
private static final String ACCESSIBILITY_MANAGER_SERVICE_CLASS =
"com.android.server.accessibility.AccessibilityManagerService$Lifecycle";
private static final String ADB_SERVICE_CLASS =
@@ -1621,7 +1623,7 @@ public final class SystemServer {
try {
mSystemServiceManager.startService(TIME_DETECTOR_SERVICE_CLASS);
} catch (Throwable e) {
- reportWtf("starting StartTimeDetectorService service", e);
+ reportWtf("starting TimeDetectorService service", e);
}
t.traceEnd();
@@ -1629,7 +1631,15 @@ public final class SystemServer {
try {
mSystemServiceManager.startService(TIME_ZONE_DETECTOR_SERVICE_CLASS);
} catch (Throwable e) {
- reportWtf("starting StartTimeZoneDetectorService service", e);
+ reportWtf("starting TimeZoneDetectorService service", e);
+ }
+ t.traceEnd();
+
+ t.traceBegin("StartLocationTimeZoneManagerService");
+ try {
+ mSystemServiceManager.startService(LOCATION_TIME_ZONE_MANAGER_SERVICE_CLASS);
+ } catch (Throwable e) {
+ reportWtf("starting LocationTimeZoneManagerService service", e);
}
t.traceEnd();
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 0789d680a80c..d61783ef59d2 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;
@@ -54,6 +55,7 @@ import static org.junit.Assert.assertNull;
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 +69,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,6 +80,7 @@ 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.Log;
import android.util.SparseArray;
@@ -100,11 +102,15 @@ 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.util.ArrayList;
+import java.util.HashSet;
+import java.util.concurrent.Executor;
@Presubmit
@RunWith(AndroidJUnit4.class)
@@ -120,8 +126,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 +141,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 +260,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 +275,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 +299,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 +333,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);
@@ -354,22 +383,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 +461,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 +479,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 +489,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 +787,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());
@@ -1081,6 +1099,49 @@ public class AlarmManagerServiceTest {
}
}
+ @Test
+ public void nonWakeupAlarmsDeferred() throws Exception {
+ final int numAlarms = 10;
+ final PendingIntent[] pis = new PendingIntent[numAlarms];
+ for (int i = 0; i < numAlarms; i++) {
+ pis[i] = getNewMockPendingIntent();
+ setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 5, pis[i]);
+ }
+ doReturn(true).when(mService).checkAllowNonWakeupDelayLocked(anyLong());
+ // Advance time past all expirations.
+ mNowElapsedTest += numAlarms + 5;
+ mTestTimer.expire();
+ assertEquals(numAlarms, mService.mPendingNonWakeupAlarms.size());
+
+ // These alarms should be sent on interactive state change to true
+ mService.interactiveStateChangedLocked(false);
+ mService.interactiveStateChangedLocked(true);
+
+ for (int i = 0; i < numAlarms; i++) {
+ verify(pis[i]).send(eq(mMockContext), eq(0), any(Intent.class), any(),
+ any(Handler.class), isNull(), any());
+ }
+ }
+
+ @Test
+ public void alarmCountOnPendingNonWakeupAlarmsRemoved() throws Exception {
+ final int numAlarms = 10;
+ final PendingIntent[] pis = new PendingIntent[numAlarms];
+ for (int i = 0; i < numAlarms; i++) {
+ pis[i] = getNewMockPendingIntent();
+ setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 5, pis[i]);
+ }
+ doReturn(true).when(mService).checkAllowNonWakeupDelayLocked(anyLong());
+ // Advance time past all expirations.
+ mNowElapsedTest += numAlarms + 5;
+ mTestTimer.expire();
+ assertEquals(numAlarms, mService.mPendingNonWakeupAlarms.size());
+ for (int i = 0; i < numAlarms; i++) {
+ mService.removeLocked(pis[i], null);
+ assertEquals(numAlarms - i - 1, mService.mAlarmsPerUid.get(TEST_CALLING_UID, 0));
+ }
+ }
+
@After
public void tearDown() {
if (mMockingSession != null) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 1bf9c2a0822e..6d40034c6000 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -46,6 +46,7 @@ import android.app.job.JobInfo;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.ServiceInfo;
import android.net.Uri;
import android.os.SystemClock;
import android.provider.MediaStore;
@@ -685,6 +686,8 @@ public class JobStatusTest {
}
private static JobStatus createJobStatus(JobInfo job) {
- return JobStatus.createFromJobInfo(job, 0, null, -1, "JobStatusTest");
+ JobStatus jobStatus = JobStatus.createFromJobInfo(job, 0, null, -1, "JobStatusTest");
+ jobStatus.serviceInfo = mock(ServiceInfo.class);
+ return jobStatus;
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 1cf133a2bbf2..18bd6b17f340 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -61,6 +61,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.ServiceInfo;
import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
import android.os.Handler;
@@ -299,6 +300,7 @@ public class QuotaControllerTest {
JobInfo jobInfo) {
JobStatus js = JobStatus.createFromJobInfo(
jobInfo, callingUid, packageName, SOURCE_USER_ID, testTag);
+ js.serviceInfo = mock(ServiceInfo.class);
// Make sure tests aren't passing just because the default bucket is likely ACTIVE.
js.setStandbyBucket(FREQUENT_INDEX);
// Make sure Doze and background-not-restricted don't affect tests.
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..b4e8825c3902 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java
@@ -66,7 +66,6 @@ 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;
@@ -616,9 +615,7 @@ public class LocationProviderManagerTest {
ILocationCallback listener = createMockGetCurrentLocationListener();
LocationRequest locationRequest = new LocationRequest.Builder(0).build();
- ICancellationSignal cancellationSignal = CancellationSignal.createTransport();
- mManager.getCurrentLocation(locationRequest, IDENTITY,
- PERMISSION_FINE, cancellationSignal, listener);
+ mManager.getCurrentLocation(locationRequest, IDENTITY, PERMISSION_FINE, listener);
Location loc = createLocation(NAME, mRandom);
mProvider.setProviderLocation(loc);
@@ -632,9 +629,8 @@ public class LocationProviderManagerTest {
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);
+ ICancellationSignal cancellationSignal = mManager.getCurrentLocation(locationRequest,
+ IDENTITY, PERMISSION_FINE, listener);
cancellationSignal.cancel();
mProvider.setProviderLocation(createLocation(NAME, mRandom));
@@ -646,9 +642,7 @@ public class LocationProviderManagerTest {
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);
+ mManager.getCurrentLocation(locationRequest, IDENTITY, PERMISSION_FINE, listener);
mProvider.setProviderAllowed(false);
mProvider.setProviderAllowed(true);
@@ -662,9 +656,7 @@ public class LocationProviderManagerTest {
ILocationCallback listener = createMockGetCurrentLocationListener();
LocationRequest locationRequest = new LocationRequest.Builder(0).build();
- ICancellationSignal cancellationSignal = CancellationSignal.createTransport();
- mManager.getCurrentLocation(locationRequest, IDENTITY,
- PERMISSION_FINE, cancellationSignal, listener);
+ mManager.getCurrentLocation(locationRequest, IDENTITY, PERMISSION_FINE, listener);
mProvider.setProviderAllowed(true);
mProvider.setProviderLocation(createLocation(NAME, mRandom));
@@ -680,9 +672,7 @@ public class LocationProviderManagerTest {
ILocationCallback listener = createMockGetCurrentLocationListener();
LocationRequest locationRequest = new LocationRequest.Builder(0).build();
- ICancellationSignal cancellationSignal = CancellationSignal.createTransport();
- mManager.getCurrentLocation(locationRequest, IDENTITY,
- PERMISSION_FINE, cancellationSignal, listener);
+ mManager.getCurrentLocation(locationRequest, IDENTITY, PERMISSION_FINE, listener);
verify(listener, times(1)).onLocation(locationCaptor.capture());
assertThat(locationCaptor.getValue()).isEqualTo(loc);
@@ -692,9 +682,7 @@ public class LocationProviderManagerTest {
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);
+ mManager.getCurrentLocation(locationRequest, IDENTITY, PERMISSION_FINE, listener);
ArgumentCaptor<OnAlarmListener> listenerCapture = ArgumentCaptor.forClass(
OnAlarmListener.class);
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java
index 0b5a6998cd0d..29d3f29ad7e1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java
@@ -34,6 +34,7 @@ import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
import com.android.server.location.listeners.ListenerMultiplexer.UpdateServiceLock;
import org.junit.Before;
@@ -318,7 +319,8 @@ public class ListenerMultiplexerTest {
}
private static class TestListenerRegistration extends
- ListenerRegistration<Integer, Consumer<TestListenerRegistration>> {
+ RequestListenerRegistration<Integer, Consumer<TestListenerRegistration>,
+ ListenerOperation<Consumer<TestListenerRegistration>>> {
boolean mActive = true;
@@ -329,8 +331,10 @@ public class ListenerMultiplexerTest {
}
private static class TestMultiplexer extends
- ListenerMultiplexer<Consumer<TestListenerRegistration>, Integer,
- Consumer<TestListenerRegistration>, TestListenerRegistration, Integer> {
+ ListenerMultiplexer<Consumer<TestListenerRegistration>,
+ Consumer<TestListenerRegistration>,
+ ListenerOperation<Consumer<TestListenerRegistration>>, TestListenerRegistration,
+ Integer> {
boolean mRegistered;
int mMergedRequest;
@@ -341,6 +345,11 @@ public class ListenerMultiplexerTest {
mCallbacks = callbacks;
}
+ @Override
+ public String getTag() {
+ return "TestMultiplexer";
+ }
+
public void addListener(Integer request, Consumer<TestListenerRegistration> consumer) {
addRegistration(consumer, new TestListenerRegistration(request, consumer));
}
@@ -369,7 +378,8 @@ public class ListenerMultiplexerTest {
}
@Override
- protected boolean registerWithService(Integer mergedRequest) {
+ protected boolean registerWithService(Integer mergedRequest,
+ Collection<TestListenerRegistration> registrations) {
mRegistered = true;
mMergedRequest = mergedRequest;
return true;
@@ -418,7 +428,8 @@ public class ListenerMultiplexerTest {
}
@Override
- protected Integer mergeRequests(Collection<TestListenerRegistration> testRegistrations) {
+ protected Integer mergeRegistrations(
+ Collection<TestListenerRegistration> testRegistrations) {
int max = Integer.MIN_VALUE;
for (TestListenerRegistration registration : testRegistrations) {
if (registration.getRequest() > max) {
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 631b4d48e9f2..4ce6411dc306 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, "is not the default supervision component",
+ assertExpectException(SecurityException.class, "no default supervision component defined",
() -> dpm.setSecondaryLockscreenEnabled(admin1, true));
assertFalse(dpm.isSecondaryLockscreenEnabled(UserHandle.of(CALLER_USER_HANDLE)));
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/hdmi/ActiveSourceActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
index 870a27417cd2..63ad53bcab4a 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));
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 83df40629085..08f558e6d99c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
@@ -19,11 +19,18 @@ import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC
import static com.google.common.truth.Truth.assertThat;
-import android.annotation.Nullable;
-import android.app.Instrumentation;
-import android.hardware.hdmi.HdmiDeviceInfo;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
import android.hardware.tv.cec.V1_0.SendMessageResult;
+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;
@@ -31,10 +38,11 @@ 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;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
@@ -44,7 +52,7 @@ import java.util.ArrayList;
@RunWith(JUnit4.class)
public class ArcInitiationActionFromAvrTest {
- private HdmiDeviceInfo mDeviceInfoForTests;
+ private Context mContextSpy;
private HdmiCecLocalDeviceAudioSystem mHdmiCecLocalDeviceAudioSystem;
private HdmiCecController mHdmiCecController;
private HdmiControlService mHdmiControlService;
@@ -52,93 +60,68 @@ public class ArcInitiationActionFromAvrTest {
private ArcInitiationActionFromAvr mAction;
private TestLooper mTestLooper = new TestLooper();
- private boolean mSendCecCommandSuccess;
- private boolean mShouldDispatchARCInitiated;
- private boolean mArcInitSent;
- private boolean mRequestActiveSourceSent;
- private Instrumentation mInstrumentation;
private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
+ @Mock private IPowerManager mIPowerManagerMock;
+ @Mock private IThermalService mIThermalServiceMock;
+ @Mock private AudioManager mAudioManager;
+
@Before
- public void setUp() {
- mDeviceInfoForTests = new HdmiDeviceInfo(1000, 1);
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
- mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
- mHdmiControlService =
- new HdmiControlService(mInstrumentation.getTargetContext()) {
- @Override
- void sendCecCommand(
- HdmiCecMessage command, @Nullable SendMessageCallback callback) {
- switch (command.getOpcode()) {
- case Constants.MESSAGE_REQUEST_ACTIVE_SOURCE:
- if (callback != null) {
- callback.onSendCompleted(
- mSendCecCommandSuccess
- ? SendMessageResult.SUCCESS
- : SendMessageResult.NACK);
- }
- mRequestActiveSourceSent = true;
- break;
- case Constants.MESSAGE_INITIATE_ARC:
- if (callback != null) {
- callback.onSendCompleted(
- mSendCecCommandSuccess
- ? SendMessageResult.SUCCESS
- : SendMessageResult.NACK);
- }
- mArcInitSent = true;
- if (mShouldDispatchARCInitiated) {
- mHdmiCecLocalDeviceAudioSystem.dispatchMessage(
- HdmiCecMessageBuilder.buildReportArcInitiated(
- Constants.ADDR_TV,
- Constants.ADDR_AUDIO_SYSTEM));
- }
- break;
- default:
- }
- }
+ PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock,
+ mIThermalServiceMock, new Handler(mTestLooper.getLooper()));
+ when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
+ when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
+ when(mIPowerManagerMock.isInteractive()).thenReturn(true);
+ mHdmiControlService =
+ new HdmiControlService(mContextSpy) {
@Override
boolean isPowerStandby() {
return false;
}
@Override
- boolean isAddressAllocated() {
- return true;
+ void wakeUp() {
}
@Override
- Looper getServiceLooper() {
- return mTestLooper.getLooper();
+ PowerManager getPowerManager() {
+ return powerManager;
}
- };
- mHdmiCecLocalDeviceAudioSystem =
- new HdmiCecLocalDeviceAudioSystem(mHdmiControlService) {
@Override
- HdmiDeviceInfo getDeviceInfo() {
- return mDeviceInfoForTests;
+ AudioManager getAudioManager() {
+ return mAudioManager;
}
@Override
- void setArcStatus(boolean enabled) {
- // do nothing
+ boolean isAddressAllocated() {
+ return true;
}
@Override
- protected boolean isSystemAudioActivated() {
- return true;
+ Looper getServiceLooper() {
+ return mTestLooper.getLooper();
}
};
+ mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(mHdmiControlService) {
+ @Override
+ protected void setPreferredAddress(int addr) {
+ }
+ };
+
mHdmiCecLocalDeviceAudioSystem.init();
Looper looper = mTestLooper.getLooper();
mHdmiControlService.setIoLooper(looper);
mNativeWrapper = new FakeNativeWrapper();
- mHdmiCecController =
- HdmiCecController.createWithNativeWrapper(this.mHdmiControlService, mNativeWrapper);
+ mHdmiCecController = HdmiCecController.createWithNativeWrapper(
+ this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
@@ -150,18 +133,88 @@ public class ArcInitiationActionFromAvrTest {
mTestLooper.dispatchAll();
}
- @Ignore("b/120845532")
@Test
- public void arcInitiation_requestActiveSource() {
- mSendCecCommandSuccess = true;
- mShouldDispatchARCInitiated = true;
- mRequestActiveSourceSent = false;
- mArcInitSent = false;
+ public void arcInitiation_initiated() {
+ mHdmiCecLocalDeviceAudioSystem.addAndStartAction(mAction);
+ mTestLooper.dispatchAll();
+ HdmiCecMessage initiateArc = HdmiCecMessageBuilder.buildInitiateArc(
+ Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_TV);
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(initiateArc);
+
+ mHdmiControlService.sendCecCommand(
+ HdmiCecMessageBuilder.buildReportArcInitiated(
+ Constants.ADDR_TV,
+ Constants.ADDR_AUDIO_SYSTEM));
+ mTestLooper.dispatchAll();
+
+ assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isTrue();
+ }
+ @Test
+ public void arcInitiation_sendFailed() {
+ mNativeWrapper.setMessageSendResult(Constants.MESSAGE_INITIATE_ARC, SendMessageResult.NACK);
mHdmiCecLocalDeviceAudioSystem.addAndStartAction(mAction);
mTestLooper.dispatchAll();
+ HdmiCecMessage initiateArc = HdmiCecMessageBuilder.buildInitiateArc(
+ Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_TV);
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(initiateArc);
+
+ assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
+ }
- assertThat(mArcInitSent).isTrue();
- assertThat(mRequestActiveSourceSent).isTrue();
+ @Test
+ public void arcInitiation_terminated() {
+ mHdmiCecLocalDeviceAudioSystem.addAndStartAction(mAction);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage initiateArc = HdmiCecMessageBuilder.buildInitiateArc(
+ Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_TV);
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(initiateArc);
+
+ mHdmiControlService.handleCecCommand(HdmiCecMessageBuilder.buildReportArcTerminated(
+ Constants.ADDR_TV,
+ Constants.ADDR_AUDIO_SYSTEM));
+ mTestLooper.dispatchAll();
+
+ assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
+ }
+
+ @Test
+ public void arcInitiation_abort() {
+ mHdmiCecLocalDeviceAudioSystem.addAndStartAction(mAction);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage initiateArc = HdmiCecMessageBuilder.buildInitiateArc(
+ Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_TV);
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(initiateArc);
+
+ mHdmiControlService.handleCecCommand(
+ HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ Constants.ADDR_TV,
+ Constants.ADDR_AUDIO_SYSTEM, Constants.MESSAGE_INITIATE_ARC,
+ Constants.ABORT_REFUSED));
+ mTestLooper.dispatchAll();
+
+ assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
+ }
+
+ //Fail
+ @Test
+ public void arcInitiation_timeout() {
+ mHdmiCecLocalDeviceAudioSystem.addAndStartAction(mAction);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage initiateArc = HdmiCecMessageBuilder.buildInitiateArc(
+ Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_TV);
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(initiateArc);
+
+ mTestLooper.moveTimeForward(1001);
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isTrue();
}
}
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 dc326eefbc9b..4afbbf741102 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
@@ -15,13 +15,22 @@
*/
package com.android.server.hdmi;
+import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
+
import static com.google.common.truth.Truth.assertThat;
-import android.annotation.Nullable;
-import android.app.Instrumentation;
-import android.hardware.hdmi.HdmiDeviceInfo;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
import android.hardware.tv.cec.V1_0.SendMessageResult;
+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;
@@ -29,10 +38,13 @@ 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;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
/** Tests for {@link ArcTerminationActionFromAvr} */
@SmallTest
@@ -40,45 +52,47 @@ import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class ArcTerminationActionFromAvrTest {
- private HdmiDeviceInfo mDeviceInfoForTests;
+ private Context mContextSpy;
private HdmiCecLocalDeviceAudioSystem mHdmiCecLocalDeviceAudioSystem;
private ArcTerminationActionFromAvr mAction;
+ private HdmiCecController mHdmiCecController;
+ private HdmiControlService mHdmiControlService;
+ private FakeNativeWrapper mNativeWrapper;
+
private TestLooper mTestLooper = new TestLooper();
- private boolean mSendCecCommandSuccess;
- private boolean mShouldDispatchReportArcTerminated;
- private Instrumentation mInstrumentation;
- @Nullable private Boolean mArcEnabled = null;
+ private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
+
+ @Mock private IPowerManager mIPowerManagerMock;
+ @Mock private IThermalService mIThermalServiceMock;
+ @Mock private AudioManager mAudioManager;
@Before
- public void setUp() {
- mDeviceInfoForTests = new HdmiDeviceInfo(1000, 1);
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
- mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+
+ PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock,
+ mIThermalServiceMock, new Handler(mTestLooper.getLooper()));
+ when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
+ when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
+ when(mIPowerManagerMock.isInteractive()).thenReturn(true);
+
+ mHdmiControlService =
+ new HdmiControlService(mContextSpy) {
+ @Override
+ void wakeUp() {
+ }
- HdmiControlService hdmiControlService =
- new HdmiControlService(mInstrumentation.getTargetContext()) {
@Override
- void sendCecCommand(
- HdmiCecMessage command, @Nullable SendMessageCallback callback) {
- switch (command.getOpcode()) {
- case Constants.MESSAGE_TERMINATE_ARC:
- if (callback != null) {
- callback.onSendCompleted(
- mSendCecCommandSuccess
- ? SendMessageResult.SUCCESS
- : SendMessageResult.NACK);
- }
- if (mShouldDispatchReportArcTerminated) {
- mHdmiCecLocalDeviceAudioSystem.dispatchMessage(
- HdmiCecMessageBuilder.buildReportArcTerminated(
- Constants.ADDR_TV,
- mHdmiCecLocalDeviceAudioSystem.mAddress));
- }
- break;
- default:
- throw new IllegalArgumentException("Unexpected message");
- }
+ PowerManager getPowerManager() {
+ return powerManager;
+ }
+
+ @Override
+ AudioManager getAudioManager() {
+ return mAudioManager;
}
@Override
@@ -97,55 +111,71 @@ public class ArcTerminationActionFromAvrTest {
}
};
- mHdmiCecLocalDeviceAudioSystem =
- new HdmiCecLocalDeviceAudioSystem(hdmiControlService) {
- @Override
- HdmiDeviceInfo getDeviceInfo() {
- return mDeviceInfoForTests;
- }
-
- @Override
- void setArcStatus(boolean enabled) {
- mArcEnabled = enabled;
- }
- };
- mHdmiCecLocalDeviceAudioSystem.init();
Looper looper = mTestLooper.getLooper();
- hdmiControlService.setIoLooper(looper);
-
+ mHdmiControlService.setIoLooper(looper);
+ mNativeWrapper = new FakeNativeWrapper();
+ mHdmiCecController = HdmiCecController.createWithNativeWrapper(
+ this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
+ mHdmiControlService.setCecController(mHdmiCecController);
+ mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
+ mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
+ mHdmiControlService.initPortInfo();
+
+ mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(mHdmiControlService) {
+ @Override
+ protected void setPreferredAddress(int addr) {
+ }
+ };
+ mHdmiCecLocalDeviceAudioSystem.init();
mAction = new ArcTerminationActionFromAvr(mHdmiCecLocalDeviceAudioSystem);
+
+ mLocalDevices.add(mHdmiCecLocalDeviceAudioSystem);
+ mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mHdmiCecLocalDeviceAudioSystem.setArcStatus(true);
+ mTestLooper.dispatchAll();
}
@Test
- @Ignore("b/120845532")
- public void testSendMessage_notSuccess() {
- mSendCecCommandSuccess = false;
- mShouldDispatchReportArcTerminated = false;
+ public void testSendMessage_sendFailed() {
+ mNativeWrapper.setMessageSendResult(Constants.MESSAGE_TERMINATE_ARC,
+ SendMessageResult.NACK);
mHdmiCecLocalDeviceAudioSystem.addAndStartAction(mAction);
-
mTestLooper.dispatchAll();
- assertThat(mArcEnabled).isNull();
+ HdmiCecMessage terminateArc = HdmiCecMessageBuilder.buildTerminateArc(
+ Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_TV);
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(terminateArc);
+
+ assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
}
@Test
- public void testReportArcTerminated_notReceived() {
- mSendCecCommandSuccess = true;
- mShouldDispatchReportArcTerminated = false;
+ public void testReportArcTerminated_timeout() {
mHdmiCecLocalDeviceAudioSystem.addAndStartAction(mAction);
-
- mTestLooper.moveTimeForward(1000);
mTestLooper.dispatchAll();
- assertThat(mArcEnabled).isNull();
+ HdmiCecMessage terminateArc = HdmiCecMessageBuilder.buildTerminateArc(
+ Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_TV);
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(terminateArc);
+
+ assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isTrue();
}
@Test
public void testReportArcTerminated_received() {
- mSendCecCommandSuccess = true;
- mShouldDispatchReportArcTerminated = true;
mHdmiCecLocalDeviceAudioSystem.addAndStartAction(mAction);
+ mTestLooper.dispatchAll();
+ HdmiCecMessage terminateArc = HdmiCecMessageBuilder.buildTerminateArc(
+ Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_TV);
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(terminateArc);
- mTestLooper.moveTimeForward(1000);
+ HdmiCecMessage arcTerminatedResponse = HdmiCecMessageBuilder.buildReportArcTerminated(
+ Constants.ADDR_TV, Constants.ADDR_AUDIO_SYSTEM);
+
+ mHdmiControlService.handleCecCommand(arcTerminatedResponse);
mTestLooper.dispatchAll();
- assertThat(mArcEnabled).isFalse();
+
+ 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 7538468fbe31..01f0a3d398df 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
@@ -24,6 +24,7 @@ import com.android.server.hdmi.HdmiCecController.NativeWrapper;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
/** Fake {@link NativeWrapper} useful for testing. */
@@ -48,6 +49,7 @@ final class FakeNativeWrapper implements NativeWrapper {
};
private final List<HdmiCecMessage> mResultMessages = new ArrayList<>();
+ private final HashMap<Integer, Integer> mMessageSendResult = new HashMap<>();
private int mMyPhysicalAddress = 0;
private HdmiPortInfo[] mHdmiPortInfo = null;
@@ -65,9 +67,10 @@ final class FakeNativeWrapper implements NativeWrapper {
if (body.length == 0) {
return mPollAddressResponse[dstAddress];
} else {
- mResultMessages.add(HdmiCecMessageBuilder.of(srcAddress, dstAddress, body));
+ HdmiCecMessage message = HdmiCecMessageBuilder.of(srcAddress, dstAddress, body);
+ mResultMessages.add(message);
+ return mMessageSendResult.getOrDefault(message.getOpcode(), SendMessageResult.SUCCESS);
}
- return SendMessageResult.SUCCESS;
}
@Override
@@ -132,6 +135,10 @@ final class FakeNativeWrapper implements NativeWrapper {
mPollAddressResponse[logicalAddress] = response;
}
+ public void setMessageSendResult(int opcode, int result) {
+ mMessageSendResult.put(opcode, result);
+ }
+
@VisibleForTesting
protected void setPhysicalAddress(int physicalAddress) {
mMyPhysicalAddress = physicalAddress;
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..dd4c0814a441 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
@@ -89,8 +89,8 @@ public class HdmiCecControllerTest {
mMyLooper = mTestLooper.getLooper();
mHdmiControlService = new MyHdmiControlService(InstrumentationRegistry.getTargetContext());
mNativeWrapper = new FakeNativeWrapper();
- mHdmiCecController =
- HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper);
+ mHdmiCecController = HdmiCecController.createWithNativeWrapper(
+ mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
}
/** Tests for {@link HdmiCecController#allocateLogicalAddress} */
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..415ae0731edb 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -188,8 +188,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));
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..d160a3fab186 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -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));
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..7560a34e63cc 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.
*/
@@ -158,8 +158,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));
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..4849dd417041 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));
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
new file mode 100644
index 000000000000..dbaad6633e98
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java
@@ -0,0 +1,528 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 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.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.assertTrue;
+import static org.junit.Assert.fail;
+
+import static java.util.Arrays.asList;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.location.timezone.LocationTimeZoneEvent;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+import android.util.IndentingPrintWriter;
+
+import com.android.server.timezonedetector.ConfigurationInternal;
+import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
+import com.android.server.timezonedetector.TestState;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/** Tests for {@link ControllerImpl}. */
+@Presubmit
+public class ControllerImplTest {
+
+ private static final long ARBITRARY_TIME = 12345L;
+
+ private static final LocationTimeZoneEvent USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1 =
+ createLocationTimeZoneEvent(USER1_ID, EVENT_TYPE_SUCCESS, asList("Europe/London"));
+ private static final LocationTimeZoneEvent USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2 =
+ 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 TestThreadingDomain mTestThreadingDomain;
+ private TestCallback mTestCallback;
+ private TestLocationTimeZoneProvider mTestLocationTimeZoneProvider;
+
+ @Before
+ public void setUp() {
+ // For simplicity, the TestThreadingDomain uses the test's main thread. To execute posted
+ // runnables, the test must call methods on mTestThreadingDomain otherwise those runnables
+ // will never get a chance to execute.
+ mTestThreadingDomain = new TestThreadingDomain();
+ mTestCallback = new TestCallback(mTestThreadingDomain);
+ mTestLocationTimeZoneProvider =
+ new TestLocationTimeZoneProvider(mTestThreadingDomain, "primary");
+ }
+
+ @Test
+ public void initialState_enabled() {
+ ControllerImpl controllerImpl =
+ new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+ TestEnvironment testEnvironment = new TestEnvironment(
+ mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ controllerImpl.initialize(testEnvironment, mTestCallback);
+
+ mTestLocationTimeZoneProvider.assertInitialized();
+
+ mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain.assertNextQueueItemIsDelayed(UNCERTAINTY_DELAY);
+ mTestCallback.assertNoSuggestionMade();
+ }
+
+ @Test
+ public void initialState_disabled() {
+ ControllerImpl controllerImpl =
+ new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+ TestEnvironment testEnvironment = new TestEnvironment(
+ mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_DISABLED);
+ controllerImpl.initialize(testEnvironment, mTestCallback);
+
+ mTestLocationTimeZoneProvider.assertInitialized();
+
+ mTestLocationTimeZoneProvider.assertIsDisabled();
+ mTestThreadingDomain.assertQueueEmpty();
+ mTestCallback.assertNoSuggestionMade();
+ }
+
+ @Test
+ public void enabled_uncertaintySuggestionSentIfNoEventReceived() {
+ ControllerImpl controllerImpl =
+ new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+ TestEnvironment testEnvironment = new TestEnvironment(
+ mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ controllerImpl.initialize(testEnvironment, mTestCallback);
+
+ mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertNoSuggestionMade();
+ mTestThreadingDomain.assertSingleDelayedQueueItem(UNCERTAINTY_DELAY);
+
+ // Simulate time passing with no event being received.
+ mTestThreadingDomain.executeNext();
+
+ mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertUncertainSuggestionMadeAndCommit();
+ mTestThreadingDomain.assertQueueEmpty();
+ }
+
+ @Test
+ public void enabled_uncertaintySuggestionCancelledIfEventReceived() {
+ ControllerImpl controllerImpl =
+ new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+ TestEnvironment testEnvironment = new TestEnvironment(
+ mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ controllerImpl.initialize(testEnvironment, mTestCallback);
+
+ mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain.assertSingleDelayedQueueItem(UNCERTAINTY_DELAY);
+ mTestCallback.assertNoSuggestionMade();
+
+ // 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(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
+
+ mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain.assertQueueEmpty();
+ mTestCallback.assertSuggestionMadeAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
+ }
+
+ @Test
+ public void enabled_repeatedCertainty() {
+ ControllerImpl controllerImpl =
+ new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+ TestEnvironment testEnvironment = new TestEnvironment(
+ mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ controllerImpl.initialize(testEnvironment, mTestCallback);
+
+ mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain.assertSingleDelayedQueueItem(UNCERTAINTY_DELAY);
+ mTestCallback.assertNoSuggestionMade();
+
+ // 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(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
+
+ mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain.assertQueueEmpty();
+ mTestCallback.assertSuggestionMadeAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
+
+ // A second, identical event should not cause another suggestion.
+ mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
+
+ mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain.assertQueueEmpty();
+ mTestCallback.assertNoSuggestionMade();
+
+ // And a third, different event should cause another suggestion.
+ mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
+
+ mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain.assertQueueEmpty();
+ mTestCallback.assertSuggestionMadeAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getTimeZoneIds());
+ }
+
+ @Test
+ public void enabled_briefUncertaintyTriggersNoSuggestion() {
+ ControllerImpl controllerImpl =
+ new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+ TestEnvironment testEnvironment = new TestEnvironment(
+ mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ controllerImpl.initialize(testEnvironment, mTestCallback);
+
+ mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain.assertSingleDelayedQueueItem(UNCERTAINTY_DELAY);
+ mTestCallback.assertNoSuggestionMade();
+
+ // 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(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
+
+ mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain.assertQueueEmpty();
+ mTestCallback.assertSuggestionMadeAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
+
+ // Uncertainty should cause
+ mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
+
+ mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain.assertSingleDelayedQueueItem(UNCERTAINTY_DELAY);
+ mTestCallback.assertNoSuggestionMade();
+
+ // And a third event should cause yet another suggestion.
+ mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
+
+ mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain.assertQueueEmpty();
+ mTestCallback.assertSuggestionMadeAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getTimeZoneIds());
+ }
+
+ @Test
+ public void configChanges_enableAndDisable() {
+ ControllerImpl controllerImpl =
+ new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+ TestEnvironment testEnvironment = new TestEnvironment(
+ mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_DISABLED);
+ controllerImpl.initialize(testEnvironment, mTestCallback);
+
+ mTestLocationTimeZoneProvider.assertIsDisabled();
+ mTestThreadingDomain.assertQueueEmpty();
+ mTestCallback.assertNoSuggestionMade();
+
+ // 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);
+ mTestCallback.assertNoSuggestionMade();
+
+ // Now signal a config change so that geo detection is disabled.
+ testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED);
+
+ mTestLocationTimeZoneProvider.assertIsDisabled();
+ mTestThreadingDomain.assertQueueEmpty();
+ mTestCallback.assertNoSuggestionMade();
+ }
+
+ @Test
+ public void configChanges_disableWithPreviousSuggestion() {
+ ControllerImpl controllerImpl =
+ new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+ TestEnvironment testEnvironment = new TestEnvironment(
+ mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ controllerImpl.initialize(testEnvironment, mTestCallback);
+
+ mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain.assertSingleDelayedQueueItem(UNCERTAINTY_DELAY);
+ mTestCallback.assertNoSuggestionMade();
+
+ // 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(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
+
+ mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain.assertQueueEmpty();
+ mTestCallback.assertSuggestionMadeAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
+
+ // Simulate the user disabling the provider.
+ testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_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);
+ }
+
+ @Test
+ public void configChanges_userSwitch_enabledToEnabled() {
+ ControllerImpl controllerImpl =
+ new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+ TestEnvironment testEnvironment = new TestEnvironment(
+ mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ 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);
+ mTestCallback.assertNoSuggestionMade();
+
+ // Have the provider suggest a time zone.
+ mTestLocationTimeZoneProvider.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();
+ mTestCallback.assertSuggestionMadeAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
+
+ // 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);
+ mTestCallback.assertNoSuggestionMade();
+
+ // Simulate no event being received, and time passing.
+ mTestThreadingDomain.executeNext();
+
+ mTestLocationTimeZoneProvider.assertIsEnabled(USER2_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain.assertQueueEmpty();
+ mTestCallback.assertUncertainSuggestionMadeAndCommit();
+ }
+
+ private static LocationTimeZoneEvent createLocationTimeZoneEvent(@UserIdInt int userId,
+ int eventType, @Nullable List<String> timeZoneIds) {
+ LocationTimeZoneEvent.Builder builder = new LocationTimeZoneEvent.Builder()
+ .setElapsedRealtimeNanos(ARBITRARY_TIME)
+ .setUserHandle(UserHandle.of(userId))
+ .setEventType(eventType);
+ if (timeZoneIds != null) {
+ builder.setTimeZoneIds(timeZoneIds);
+ }
+ return builder.build();
+ }
+
+ private static class TestEnvironment extends LocationTimeZoneProviderController.Environment {
+
+ private final LocationTimeZoneProviderController mController;
+ private ConfigurationInternal mConfigurationInternal;
+
+ TestEnvironment(ThreadingDomain threadingDomain,
+ LocationTimeZoneProviderController controller,
+ ConfigurationInternal configurationInternal) {
+ super(threadingDomain);
+ mController = Objects.requireNonNull(controller);
+ mConfigurationInternal = Objects.requireNonNull(configurationInternal);
+ }
+
+ @Override
+ ConfigurationInternal getCurrentUserConfigurationInternal() {
+ return mConfigurationInternal;
+ }
+
+ void simulateConfigChange(ConfigurationInternal newConfig) {
+ ConfigurationInternal oldConfig = mConfigurationInternal;
+ mConfigurationInternal = Objects.requireNonNull(newConfig);
+ if (Objects.equals(oldConfig, newConfig)) {
+ fail("Bad test? No config change when one was expected");
+ }
+ mController.onConfigChanged();
+ }
+ }
+
+ private static class TestCallback extends LocationTimeZoneProviderController.Callback {
+
+ private TestState<GeolocationTimeZoneSuggestion> mLatestSuggestion = new TestState<>();
+
+ TestCallback(ThreadingDomain threadingDomain) {
+ super(threadingDomain);
+ }
+
+ @Override
+ void suggest(GeolocationTimeZoneSuggestion suggestion) {
+ mLatestSuggestion.set(suggestion);
+ }
+
+ void assertSuggestionMadeAndCommit(@Nullable List<String> expectedZoneIds) {
+ mLatestSuggestion.assertHasBeenSet();
+ assertEquals(expectedZoneIds, mLatestSuggestion.getLatest().getZoneIds());
+ mLatestSuggestion.commitLatest();
+ }
+
+ void assertNoSuggestionMade() {
+ mLatestSuggestion.assertHasNotBeenSet();
+ }
+
+ void assertUncertainSuggestionMadeAndCommit() {
+ // An "uncertain" suggestion has null time zone IDs.
+ assertSuggestionMadeAndCommit(null);
+ }
+ }
+
+ private static class TestLocationTimeZoneProvider extends LocationTimeZoneProvider {
+
+ /** Used to track historic provider states for tests. */
+ private final TestState<ProviderState> mTestProviderState = new TestState<>();
+ private boolean mInitialized;
+
+ /**
+ * Creates the instance.
+ */
+ TestLocationTimeZoneProvider(ThreadingDomain threadingDomain, String providerName) {
+ super(threadingDomain, providerName);
+ }
+
+ @Override
+ void onInitialize() {
+ mInitialized = true;
+ }
+
+ @Override
+ void onSetCurrentState(ProviderState newState) {
+ mTestProviderState.set(newState);
+ }
+
+ @Override
+ void onEnable() {
+ // Nothing needed for tests.
+ }
+
+ @Override
+ void onDisable() {
+ // Nothing needed for tests.
+ }
+
+ @Override
+ void logWarn(String msg) {
+ System.out.println(msg);
+ }
+
+ @Override
+ public void dump(IndentingPrintWriter pw, String[] args) {
+ // Nothing needed for tests.
+ }
+
+ /** Asserts that {@link #initialize(ProviderListener)} has been called. */
+ void assertInitialized() {
+ assertTrue(mInitialized);
+ }
+
+ void assertIsDisabled() {
+ // Disabled providers don't hold config.
+ assertConfig(null);
+ assertIsEnabledAndCommit(false);
+ }
+
+ /**
+ * Asserts the provider's config matches the expected, and the current state is set
+ * accordinly. Commits the latest changes to the state.
+ */
+ void assertIsEnabled(@NonNull ConfigurationInternal expectedConfig) {
+ assertConfig(expectedConfig);
+
+ boolean expectIsEnabled = expectedConfig.getAutoDetectionEnabledBehavior();
+ assertIsEnabledAndCommit(expectIsEnabled);
+ }
+
+ private void assertIsEnabledAndCommit(boolean enabled) {
+ ProviderState currentState = mCurrentState.get();
+ if (enabled) {
+ assertEquals(PROVIDER_STATE_ENABLED, currentState.stateEnum);
+ } else {
+ assertEquals(PROVIDER_STATE_DISABLED, currentState.stateEnum);
+ }
+ mTestProviderState.commitLatest();
+ }
+
+ void assertConfig(@NonNull ConfigurationInternal expectedConfig) {
+ ProviderState currentState = mCurrentState.get();
+ assertEquals(expectedConfig, currentState.currentUserConfiguration);
+ }
+
+ void simulateLocationTimeZoneEvent(@NonNull LocationTimeZoneEvent event) {
+ handleLocationTimeZoneEvent(event);
+ }
+
+ /**
+ * Asserts the most recent state changes. The ordering is such that the last element in the
+ * provided array is expected to be the current state.
+ */
+ void assertStateChangesAndCommit(int... expectedProviderStates) {
+ if (expectedProviderStates.length == 0) {
+ mTestProviderState.assertHasNotBeenSet();
+ } else {
+ mTestProviderState.assertChangeCount(expectedProviderStates.length);
+
+ List<ProviderState> previousProviderStates = new ArrayList<>();
+ for (int i = 0; i < expectedProviderStates.length; i++) {
+ previousProviderStates.add(mTestProviderState.getPrevious(i));
+ }
+ // The loop above will produce a list with the most recent state in element 0. So,
+ // reverse the list as the arguments to this method are expected to be in order
+ // oldest...latest.
+ Collections.reverse(previousProviderStates);
+
+ boolean allMatch = true;
+ for (int i = 0; i < expectedProviderStates.length; i++) {
+ allMatch = allMatch && expectedProviderStates[i]
+ == previousProviderStates.get(i).stateEnum;
+ }
+ if (!allMatch) {
+ fail("Provider state enums expected=" + Arrays.toString(expectedProviderStates)
+ + " but states were"
+ + " actually=" + previousProviderStates);
+ }
+ }
+ mTestProviderState.commitLatest();
+ }
+ }
+}
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
new file mode 100644
index 000000000000..cbaf0f391375
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/HandlerThreadingDomainTest.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.platform.test.annotations.Presubmit;
+
+import com.android.server.location.timezone.ThreadingDomain.SingleRunnableQueue;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/** Tests for {@link HandlerThreadingDomain}. */
+@Presubmit
+public class HandlerThreadingDomainTest {
+
+ private HandlerThread mHandlerThread;
+ private Handler mTestHandler;
+
+ @Before
+ public void setUp() {
+ mHandlerThread = new HandlerThread("HandlerThreadingDomainTest");
+ mHandlerThread.start();
+ mTestHandler = new Handler(mHandlerThread.getLooper());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mHandlerThread.quit();
+ mHandlerThread.join();
+ }
+
+ @Test
+ public void getLockObject() {
+ ThreadingDomain domain = new HandlerThreadingDomain(mTestHandler);
+ assertSame("LockObject must be consistent", domain.getLockObject(), domain.getLockObject());
+ }
+
+ @Test
+ public void assertCurrentThread() throws Exception {
+ ThreadingDomain domain = new HandlerThreadingDomain(mTestHandler);
+
+ // Expect an exception (current thread != handler thread)
+ try {
+ domain.assertCurrentThread();
+ fail("Expected exception");
+ } catch (RuntimeException expected) {
+ // Expected
+ }
+
+ // Expect no exception (current thread == handler thread)
+ AtomicBoolean exceptionThrown = new AtomicBoolean(true);
+ LatchedRunnable testCode = new LatchedRunnable(() -> {
+ domain.assertCurrentThread();
+ exceptionThrown.set(false);
+ });
+ mTestHandler.post(testCode);
+ testCode.assertCompletesWithin(60, TimeUnit.SECONDS);
+ assertFalse(exceptionThrown.get());
+ }
+
+ @Test
+ public void post() throws Exception {
+ ThreadingDomain domain = new HandlerThreadingDomain(mTestHandler);
+ AtomicBoolean ranOnExpectedThread = new AtomicBoolean(false);
+ LatchedRunnable testLogic = new LatchedRunnable(() -> {
+ ranOnExpectedThread.set(Thread.currentThread() == mTestHandler.getLooper().getThread());
+ });
+ domain.post(testLogic);
+ testLogic.assertCompletesWithin(60, TimeUnit.SECONDS);
+ assertTrue(ranOnExpectedThread.get());
+ }
+
+ @Test
+ public void postDelayed() throws Exception {
+ ThreadingDomain domain = new HandlerThreadingDomain(mTestHandler);
+
+ long beforeExecutionNanos = System.nanoTime();
+ AtomicBoolean ranOnExpectedThread = new AtomicBoolean(false);
+ LatchedRunnable testLogic = new LatchedRunnable(() -> {
+ ranOnExpectedThread.set(Thread.currentThread() == mTestHandler.getLooper().getThread());
+ });
+ domain.postDelayed(testLogic, 5000);
+
+ testLogic.assertCompletesWithin(60, TimeUnit.SECONDS);
+ assertTrue(ranOnExpectedThread.get());
+
+ long afterExecutionNanos = System.nanoTime();
+ assertTrue(afterExecutionNanos - beforeExecutionNanos >= TimeUnit.SECONDS.toNanos(5));
+ }
+
+ @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 {
+ ThreadingDomain domain = new HandlerThreadingDomain(mTestHandler);
+ SingleRunnableQueue singleRunnableQueue = domain.createSingleRunnableQueue();
+
+ long beforeExecutionNanos = System.nanoTime();
+
+ Runnable noOpRunnable = () -> {
+ // Deliberately do nothing
+ };
+ LatchedRunnable firstRunnable = new LatchedRunnable(noOpRunnable);
+ LatchedRunnable secondRunnable = new LatchedRunnable(noOpRunnable);
+
+ // Calls to SingleRunnableQueue must be made on the handler thread it is associated with,
+ // so this uses runWithScissors() to block until the runDelayedTestRunnable has completed.
+ Runnable runDelayedTestRunnable = () -> {
+ singleRunnableQueue.runDelayed(firstRunnable, TimeUnit.SECONDS.toMillis(10));
+
+ // The second runnable posted must clear the first.
+ singleRunnableQueue.runDelayed(secondRunnable, TimeUnit.SECONDS.toMillis(10));
+ };
+ mTestHandler.runWithScissors(runDelayedTestRunnable, TimeUnit.SECONDS.toMillis(60));
+
+ // Now wait for the second runnable to complete
+ secondRunnable.assertCompletesWithin(60, TimeUnit.SECONDS);
+ assertFalse(firstRunnable.isComplete());
+
+ long afterExecutionNanos = System.nanoTime();
+ assertTrue(afterExecutionNanos - beforeExecutionNanos >= TimeUnit.SECONDS.toNanos(10));
+ }
+
+ private static boolean awaitWithRuntimeException(
+ CountDownLatch latch, long timeout, TimeUnit timeUnit) {
+ try {
+ return latch.await(timeout, timeUnit);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static class LatchedRunnable implements Runnable {
+
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+ private final Runnable mRunnable;
+
+ LatchedRunnable(Runnable mRunnable) {
+ this.mRunnable = Objects.requireNonNull(mRunnable);
+ }
+
+ @Override
+ public void run() {
+ try {
+ mRunnable.run();
+ } finally {
+ mLatch.countDown();
+ }
+ }
+
+ boolean isComplete() {
+ return mLatch.getCount() == 0;
+ }
+
+ boolean waitForCompletion(long timeout, TimeUnit unit) {
+ return awaitWithRuntimeException(mLatch, timeout, unit);
+ }
+
+ void assertCompletesWithin(long timeout, TimeUnit unit) {
+ assertTrue("Runnable did not execute in time", waitForCompletion(timeout, unit));
+ }
+ }
+}
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
new file mode 100644
index 000000000000..7c882fc1f154
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/NullLocationTimeZoneProviderTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 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_PERM_FAILED;
+import static com.android.server.location.timezone.TestSupport.USER1_CONFIG_GEO_DETECTION_ENABLED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import android.platform.test.annotations.Presubmit;
+import android.util.IndentingPrintWriter;
+
+import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState;
+import com.android.server.timezonedetector.ConfigurationInternal;
+import com.android.server.timezonedetector.TestState;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests for {@link NullLocationTimeZoneProvider} and, indirectly, the class it extends
+ * {@link LocationTimeZoneProvider}.
+ */
+@Presubmit
+public class NullLocationTimeZoneProviderTest {
+
+ private TestThreadingDomain mTestThreadingDomain;
+
+ private TestController mTestController;
+
+ @Before
+ public void setUp() {
+ mTestThreadingDomain = new TestThreadingDomain();
+ mTestController = new TestController(mTestThreadingDomain);
+ }
+
+ @Test
+ public void initialization() {
+ String providerName = "primary";
+ NullLocationTimeZoneProvider provider =
+ new NullLocationTimeZoneProvider(mTestThreadingDomain, providerName);
+ provider.initialize(providerState -> mTestController.onProviderStateChange(providerState));
+
+ ProviderState currentState = provider.getCurrentState();
+ assertEquals(PROVIDER_STATE_DISABLED, currentState.stateEnum);
+ assertNull(currentState.currentUserConfiguration);
+ assertSame(provider, currentState.provider);
+ mTestThreadingDomain.assertQueueEmpty();
+ }
+
+ @Test
+ public void enableSchedulesPermFailure() {
+ String providerName = "primary";
+ NullLocationTimeZoneProvider provider =
+ new NullLocationTimeZoneProvider(mTestThreadingDomain, providerName);
+ provider.initialize(providerState -> mTestController.onProviderStateChange(providerState));
+
+ ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED;
+ provider.enable(config);
+
+ // The StubbedProvider should enters enabled state, but immediately schedule a runnable to
+ // switch to perm failure.
+ ProviderState currentState = provider.getCurrentState();
+ assertSame(provider, currentState.provider);
+ assertEquals(PROVIDER_STATE_ENABLED, currentState.stateEnum);
+ assertEquals(config, currentState.currentUserConfiguration);
+ mTestThreadingDomain.assertSingleImmediateQueueItem();
+ // Entering enabled() does not trigger an onProviderStateChanged() as it is requested by the
+ // controller.
+ mTestController.assertProviderChangeNotTriggered();
+
+ // Check the queued runnable causes the provider to go into perm failed state.
+ mTestThreadingDomain.executeNext();
+
+ // Entering perm failed triggers an onProviderStateChanged() as it is asynchronously
+ // triggered.
+ mTestController.assertProviderChangeTriggered(PROVIDER_STATE_PERM_FAILED);
+ }
+
+ /** A test stand-in for the {@link LocationTimeZoneProviderController}. */
+ private static class TestController extends LocationTimeZoneProviderController {
+
+ private TestState<ProviderState> mProviderState = new TestState<>();
+
+ TestController(ThreadingDomain threadingDomain) {
+ super(threadingDomain);
+ }
+
+ @Override
+ void initialize(Environment environment, Callback callback) {
+ // Not needed for provider testing.
+ }
+
+ @Override
+ void onConfigChanged() {
+ // Not needed for provider testing.
+ }
+
+ void onProviderStateChange(ProviderState providerState) {
+ this.mProviderState.set(providerState);
+ }
+
+ @Override
+ public void dump(IndentingPrintWriter pw, String[] args) {
+ // Not needed for provider testing.
+ }
+
+ void assertProviderChangeTriggered(int expectedStateEnum) {
+ assertEquals(expectedStateEnum, mProviderState.getLatest().stateEnum);
+ mProviderState.commitLatest();
+ }
+
+ public void assertProviderChangeNotTriggered() {
+ mProviderState.assertHasNotBeenSet();
+ }
+ }
+}
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
new file mode 100644
index 000000000000..192ade7847b0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/TestSupport.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.server.location.timezone;
+
+import android.annotation.UserIdInt;
+
+import com.android.server.timezonedetector.ConfigurationInternal;
+
+/** Shared test support code for this package. */
+final class TestSupport {
+ static final @UserIdInt int USER1_ID = 9999;
+
+ static final ConfigurationInternal USER1_CONFIG_GEO_DETECTION_ENABLED =
+ createUserConfig(USER1_ID, true);
+
+ static final ConfigurationInternal USER1_CONFIG_GEO_DETECTION_DISABLED =
+ createUserConfig(USER1_ID, false);
+
+ static final @UserIdInt int USER2_ID = 1234567890;
+
+ static final ConfigurationInternal USER2_CONFIG_GEO_DETECTION_ENABLED =
+ createUserConfig(USER2_ID, true);
+
+ static final ConfigurationInternal USER2_CONFIG_GEO_DETECTION_DISABLED =
+ createUserConfig(USER2_ID, false);
+
+ private TestSupport() {
+ }
+
+ private static ConfigurationInternal createUserConfig(
+ @UserIdInt int userId, boolean geoDetectionEnabled) {
+ return new ConfigurationInternal.Builder(userId)
+ .setUserConfigAllowed(true)
+ .setAutoDetectionSupported(true)
+ .setAutoDetectionEnabled(true)
+ .setLocationEnabled(true)
+ .setGeoDetectionEnabled(geoDetectionEnabled)
+ .build();
+ }
+}
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
new file mode 100644
index 000000000000..70ff22d17513
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 static org.junit.Assert.assertEquals;
+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.Objects;
+
+/**
+ * A ThreadingDomain that simulates idealized post() semantics. Execution takes place in zero time,
+ * exactly when scheduled, when the test code explicitly requests it. Execution takes place on the
+ * test's main thread.
+ */
+class TestThreadingDomain extends ThreadingDomain {
+
+ static class QueuedRunnable {
+ @NonNull public final Runnable runnable;
+ @Nullable public final Object token;
+ public final long executionTimeMillis;
+
+ QueuedRunnable(@NonNull Runnable runnable, @Nullable Object token,
+ long executionTimeMillis) {
+ this.runnable = Objects.requireNonNull(runnable);
+ this.token = token;
+ this.executionTimeMillis = executionTimeMillis;
+ }
+
+ @Override
+ public String toString() {
+ return "QueuedRunnable{"
+ + "runnable=" + runnable
+ + ", token=" + token
+ + ", executionTimeMillis=" + executionTimeMillis
+ + '}';
+ }
+ }
+
+ private long mCurrentTimeMillis;
+ private LinkedList<QueuedRunnable> mQueue = new LinkedList<>();
+
+ TestThreadingDomain() {
+ // Pick an arbitrary time.
+ mCurrentTimeMillis = 123456L;
+ }
+
+ @Override
+ Thread getThread() {
+ return Thread.currentThread();
+ }
+
+ @Override
+ void post(Runnable r) {
+ mQueue.add(new QueuedRunnable(r, null, mCurrentTimeMillis));
+ }
+
+ @Override
+ void postDelayed(Runnable r, long delayMillis) {
+ mQueue.add(new QueuedRunnable(r, null, mCurrentTimeMillis + delayMillis));
+ }
+
+ @Override
+ void postDelayed(Runnable r, Object token, long delayMillis) {
+ mQueue.add(new QueuedRunnable(r, token, mCurrentTimeMillis + delayMillis));
+ }
+
+ @Override
+ void removeQueuedRunnables(Object token) {
+ mQueue.removeIf(runnable -> runnable.token != null && runnable.token.equals(token));
+ }
+
+ void assertSingleDelayedQueueItem(Duration expectedDelay) {
+ assertQueueLength(1);
+ assertNextQueueItemIsDelayed(expectedDelay);
+ }
+
+ void assertSingleImmediateQueueItem() {
+ assertQueueLength(1);
+ assertNextQueueItemIsImmediate();
+ }
+
+ void assertQueueLength(int expectedLength) {
+ assertEquals(expectedLength, mQueue.size());
+ }
+
+ void assertNextQueueItemIsImmediate() {
+ assertTrue(getNextQueueItemDelayMillis() == 0);
+ }
+
+ void assertNextQueueItemIsDelayed(Duration expectedDelay) {
+ assertTrue(getNextQueueItemDelayMillis() == expectedDelay.toMillis());
+ }
+
+ void assertQueueEmpty() {
+ assertTrue(mQueue.isEmpty());
+ }
+
+ long getNextQueueItemDelayMillis() {
+ assertQueueLength(1);
+ return mQueue.getFirst().executionTimeMillis - mCurrentTimeMillis;
+ }
+
+ void executeNext() {
+ assertQueueLength(1);
+
+ 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/timezonedetector/TestState.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TestState.java
index 7049efa1cc2f..97b8360172f2 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TestState.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TestState.java
@@ -76,7 +76,7 @@ public class TestState<T> {
/** Asserts the number of times {@link #set} has been called. */
public void assertChangeCount(int expectedCount) {
- assertEquals(expectedCount, mValues.size());
+ assertEquals(expectedCount, getChangeCount());
}
/**
@@ -89,4 +89,25 @@ public class TestState<T> {
}
return mInitialValue;
}
+
+ /** Returns the number of times {@link #set} has been called. */
+ public int getChangeCount() {
+ return mValues.size();
+ }
+
+ /**
+ * Returns an historic value of the state. Values for {@code age} can be from {@code 0}, the
+ * latest value, through {@code getChangeCount() - 1}, which returns the oldest change, to
+ * {@code getChangeCount()}, which returns the initial value. Values outside of this range will
+ * cause {@link IndexOutOfBoundsException} to be thrown.
+ */
+ public T getPrevious(int age) {
+ int size = mValues.size();
+ if (age < size) {
+ return mValues.get(size - 1 - age);
+ } else if (age == size) {
+ return mInitialValue;
+ }
+ throw new IndexOutOfBoundsException("age=" + age + " is too big.");
+ }
}
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 2aeab209162a..2c645268e190 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -112,6 +112,8 @@ public class AppStandbyControllerTests {
private static final int UID_SYSTEM_HEADFULL = 10002;
private static final String PACKAGE_SYSTEM_HEADLESS = "com.example.system.headless";
private static final int UID_SYSTEM_HEADLESS = 10003;
+ private static final String PACKAGE_WELLBEING = "com.example.wellbeing";
+ private static final int UID_WELLBEING = 10004;
private static final int USER_ID = 0;
private static final int USER_ID2 = 10;
private static final UserHandle USER_HANDLE_USER2 = new UserHandle(USER_ID2);
@@ -221,6 +223,11 @@ public class AppStandbyControllerTests {
}
@Override
+ boolean isWellbeingPackage(String packageName) {
+ return PACKAGE_WELLBEING.equals(packageName);
+ }
+
+ @Override
void updatePowerWhitelistCache() {
}
@@ -332,6 +339,12 @@ public class AppStandbyControllerTests {
pish.packageName = PACKAGE_SYSTEM_HEADLESS;
packages.add(pish);
+ PackageInfo piw = new PackageInfo();
+ piw.applicationInfo = new ApplicationInfo();
+ piw.applicationInfo.uid = UID_WELLBEING;
+ piw.packageName = PACKAGE_WELLBEING;
+ packages.add(piw);
+
doReturn(packages).when(mockPm).getInstalledPackagesAsUser(anyInt(), anyInt());
try {
for (int i = 0; i < packages.size(); ++i) {
@@ -1520,6 +1533,25 @@ public class AppStandbyControllerTests {
assertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
}
+ @Test
+ public void testWellbeingAppElevated() {
+ reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_WELLBEING);
+ assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_WELLBEING);
+ reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
+ assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
+ mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD;
+
+ // Make sure the default wellbeing app does not get lowered below WORKING_SET.
+ mController.setAppStandbyBucket(PACKAGE_WELLBEING, USER_ID, STANDBY_BUCKET_RARE,
+ REASON_MAIN_TIMEOUT);
+ assertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_WELLBEING);
+
+ // A non default wellbeing app should be able to fall lower than WORKING_SET.
+ mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
+ REASON_MAIN_TIMEOUT);
+ assertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
+ }
+
private String getAdminAppsStr(int userId) {
return getAdminAppsStr(userId, mController.getActiveAdminAppsForTest(userId));
}
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/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 94e40413f9f8..951118125f6e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -124,7 +124,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase {
spyOn(mNavBarWindow);
// Disabling this call for most tests since it can override the systemUiFlags when called.
- doReturn(0).when(mDisplayPolicy).updateSystemUiVisibilityLw();
+ doReturn(false).when(mDisplayPolicy).updateSystemUiVisibilityLw();
updateDisplayFrames();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index b77d21c0f711..a55423a7baf6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -19,9 +19,9 @@ package com.android.server.wm;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.Surface.ROTATION_0;
-import static android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_TOUCH;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
@@ -78,8 +78,7 @@ public class DisplayPolicyTests extends WindowTestsBase {
attrs.flags =
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
attrs.format = PixelFormat.OPAQUE;
- attrs.systemUiVisibility = attrs.subtreeSystemUiVisibility = win.mSystemUiVisibility =
- hasLightNavBar ? SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR : 0;
+ attrs.insetsFlags.appearance = hasLightNavBar ? APPEARANCE_LIGHT_NAVIGATION_BARS : 0;
return win;
}
@@ -103,8 +102,7 @@ public class DisplayPolicyTests extends WindowTestsBase {
attrs.flags = FLAG_NOT_FOCUSABLE | FLAG_LAYOUT_IN_SCREEN
| (drawNavBar ? FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS : 0);
attrs.format = PixelFormat.TRANSPARENT;
- attrs.systemUiVisibility = attrs.subtreeSystemUiVisibility = win.mSystemUiVisibility =
- hasLightNavBar ? SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR : 0;
+ attrs.insetsFlags.appearance = hasLightNavBar ? APPEARANCE_LIGHT_NAVIGATION_BARS : 0;
win.mHasSurface = visible;
return win;
}
@@ -163,6 +161,7 @@ public class DisplayPolicyTests extends WindowTestsBase {
@Test
public void testUpdateLightNavigationBarLw() {
+ DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
final WindowState opaqueDarkNavBar = createOpaqueFullscreen(false);
final WindowState opaqueLightNavBar = createOpaqueFullscreen(true);
@@ -171,50 +170,50 @@ public class DisplayPolicyTests extends WindowTestsBase {
final WindowState imeDrawDarkNavBar = createInputMethodWindow(true, true, false);
final WindowState imeDrawLightNavBar = createInputMethodWindow(true, true, true);
- assertEquals(SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
- DisplayPolicy.updateLightNavigationBarLw(
- SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, null, null,
+ assertEquals(APPEARANCE_LIGHT_NAVIGATION_BARS,
+ displayPolicy.updateLightNavigationBarLw(
+ APPEARANCE_LIGHT_NAVIGATION_BARS, null, null,
null, null));
- // Opaque top fullscreen window overrides SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR flag.
- assertEquals(0, DisplayPolicy.updateLightNavigationBarLw(
+ // Opaque top fullscreen window overrides APPEARANCE_LIGHT_NAVIGATION_BARS flag.
+ assertEquals(0, displayPolicy.updateLightNavigationBarLw(
0, opaqueDarkNavBar, opaqueDarkNavBar, null, opaqueDarkNavBar));
- assertEquals(0, DisplayPolicy.updateLightNavigationBarLw(
- SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, opaqueDarkNavBar, opaqueDarkNavBar, null,
+ assertEquals(0, displayPolicy.updateLightNavigationBarLw(
+ APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueDarkNavBar, opaqueDarkNavBar, null,
opaqueDarkNavBar));
- assertEquals(SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
- DisplayPolicy.updateLightNavigationBarLw(0, opaqueLightNavBar,
+ assertEquals(APPEARANCE_LIGHT_NAVIGATION_BARS,
+ displayPolicy.updateLightNavigationBarLw(0, opaqueLightNavBar,
opaqueLightNavBar, null, opaqueLightNavBar));
- assertEquals(SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
- DisplayPolicy.updateLightNavigationBarLw(SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
+ assertEquals(APPEARANCE_LIGHT_NAVIGATION_BARS,
+ displayPolicy.updateLightNavigationBarLw(APPEARANCE_LIGHT_NAVIGATION_BARS,
opaqueLightNavBar, opaqueLightNavBar, null, opaqueLightNavBar));
- // Dimming window clears SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.
- assertEquals(0, DisplayPolicy.updateLightNavigationBarLw(
+ // Dimming window clears APPEARANCE_LIGHT_NAVIGATION_BARS.
+ assertEquals(0, displayPolicy.updateLightNavigationBarLw(
0, opaqueDarkNavBar, dimming, null, dimming));
- assertEquals(0, DisplayPolicy.updateLightNavigationBarLw(
+ assertEquals(0, displayPolicy.updateLightNavigationBarLw(
0, opaqueLightNavBar, dimming, null, dimming));
- assertEquals(0, DisplayPolicy.updateLightNavigationBarLw(
- SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, opaqueDarkNavBar, dimming, null, dimming));
- assertEquals(0, DisplayPolicy.updateLightNavigationBarLw(
- SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, opaqueLightNavBar, dimming, null, dimming));
- assertEquals(0, DisplayPolicy.updateLightNavigationBarLw(
- SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, opaqueLightNavBar, dimming, imeDrawLightNavBar,
+ assertEquals(0, displayPolicy.updateLightNavigationBarLw(
+ APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueDarkNavBar, dimming, null, dimming));
+ assertEquals(0, displayPolicy.updateLightNavigationBarLw(
+ APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueLightNavBar, dimming, null, dimming));
+ assertEquals(0, displayPolicy.updateLightNavigationBarLw(
+ APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueLightNavBar, dimming, imeDrawLightNavBar,
dimming));
- // IME window clears SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
- assertEquals(0, DisplayPolicy.updateLightNavigationBarLw(
- SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, null, null, imeDrawDarkNavBar,
+ // IME window clears APPEARANCE_LIGHT_NAVIGATION_BARS
+ assertEquals(0, displayPolicy.updateLightNavigationBarLw(
+ APPEARANCE_LIGHT_NAVIGATION_BARS, null, null, imeDrawDarkNavBar,
imeDrawDarkNavBar));
- // Even if the top fullscreen has SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, IME window wins.
- assertEquals(0, DisplayPolicy.updateLightNavigationBarLw(
- SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, opaqueLightNavBar, opaqueLightNavBar,
+ // Even if the top fullscreen has APPEARANCE_LIGHT_NAVIGATION_BARS, IME window wins.
+ assertEquals(0, displayPolicy.updateLightNavigationBarLw(
+ APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueLightNavBar, opaqueLightNavBar,
imeDrawDarkNavBar, imeDrawDarkNavBar));
- // IME window should be able to use SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.
- assertEquals(SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
- DisplayPolicy.updateLightNavigationBarLw(0, opaqueDarkNavBar,
+ // IME window should be able to use APPEARANCE_LIGHT_NAVIGATION_BARS.
+ assertEquals(APPEARANCE_LIGHT_NAVIGATION_BARS,
+ displayPolicy.updateLightNavigationBarLw(0, opaqueDarkNavBar,
opaqueDarkNavBar, imeDrawLightNavBar, imeDrawLightNavBar));
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
index af8cb02a86fe..94ffcdab4fa7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
@@ -45,7 +45,6 @@ import android.util.Pair;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.Gravity;
-import android.view.View;
import android.view.WindowManagerGlobal;
import com.android.internal.R;
@@ -99,11 +98,9 @@ public class DisplayPolicyTestsBase extends WindowTestsBase {
mStatusBarWindow.mAttrs.gravity = Gravity.TOP;
addWindow(mStatusBarWindow);
- mDisplayPolicy.mLastSystemUiFlags |= View.STATUS_BAR_TRANSPARENT;
mNavBarWindow.mAttrs.gravity = Gravity.BOTTOM;
addWindow(mNavBarWindow);
- mDisplayPolicy.mLastSystemUiFlags |= View.NAVIGATION_BAR_TRANSPARENT;
// Update source frame and visibility of insets providers.
mDisplayContent.getInsetsStateController().onPostLayout();
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 608305c33168..8c02faf7ac09 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -22,7 +22,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMAR
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
@@ -38,20 +37,22 @@ 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 android.view.test.InsetsModeSession;
import androidx.test.filters.SmallTest;
-import org.junit.AfterClass;
+import com.android.server.statusbar.StatusBarManagerInternal;
+
import org.junit.Before;
-import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -59,19 +60,6 @@ import org.junit.runner.RunWith;
@Presubmit
@RunWith(WindowTestRunner.class)
public class InsetsPolicyTest extends WindowTestsBase {
- private static InsetsModeSession sInsetsModeSession;
-
- @BeforeClass
- public static void setUpOnce() {
- // To let the insets provider control the insets visibility, the insets mode has to be
- // NEW_INSETS_MODE_FULL.
- sInsetsModeSession = new InsetsModeSession(NEW_INSETS_MODE_FULL);
- }
-
- @AfterClass
- public static void tearDownOnce() {
- sInsetsModeSession.close();
- }
@Before
public void setup() {
@@ -222,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)
@@ -312,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 59f8cc8c3412..4a4974355ea2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -23,7 +23,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMAR
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
-import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -43,15 +42,11 @@ import static org.mockito.Mockito.verify;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
-import android.util.IntArray;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.test.InsetsModeSession;
import androidx.test.filters.SmallTest;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -59,19 +54,6 @@ import org.junit.runner.RunWith;
@Presubmit
@RunWith(WindowTestRunner.class)
public class InsetsStateControllerTest extends WindowTestsBase {
- private static InsetsModeSession sInsetsModeSession;
-
- @BeforeClass
- public static void setUpOnce() {
- // To let the insets provider control the insets visibility, the insets mode has to be
- // NEW_INSETS_MODE_FULL.
- sInsetsModeSession = new InsetsModeSession(NEW_INSETS_MODE_FULL);
- }
-
- @AfterClass
- public static void tearDownOnce() {
- sInsetsModeSession.close();
- }
@Test
public void testStripForDispatch_notOwn() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index edcf0d4f5501..f1540731da8b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -566,7 +566,7 @@ public class SizeCompatTests extends WindowTestsBase {
assertEquals(new Rect(mActivity.getBounds().left, 0, dh - mActivity.getBounds().right, 0),
mActivity.getLetterboxInsets());
- final StatusBarController statusBarController =
+ final BarController statusBarController =
mActivity.mDisplayContent.getDisplayPolicy().getStatusBarController();
// The activity doesn't fill the display, so the letterbox of the rotated activity is
// overlapped with the rotated content frame of status bar. Hence the status bar shouldn't
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..da3319acca24 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -318,6 +318,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);
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..e71f7ec54b0e 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,7 @@ 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.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 +243,40 @@ 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();
+ }
+
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..d2b7ac4c3b24 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,7 +1002,7 @@ public class TaskRecordTests extends WindowTestsBase {
public void testNotSpecifyOrientationByFloatingTask() {
final Task task = getTestTask();
final ActivityRecord activity = task.getTopMostActivity();
- final WindowContainer<?> taskDisplayArea = task.getParent();
+ final TaskDisplayArea taskDisplayArea = task.getDisplayArea();
activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
assertEquals(SCREEN_ORIENTATION_LANDSCAPE, taskDisplayArea.getOrientation());
@@ -1012,6 +1013,42 @@ public class TaskRecordTests extends WindowTestsBase {
}
@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
public void testNotifyOrientationChangeCausedByConfigurationChange() {
final Task task = getTestTask();
final ActivityRecord activity = task.getTopMostActivity();
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/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 2510385f4580..5b7cf5a72f0a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -27,6 +27,8 @@ import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+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.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -164,4 +166,14 @@ public class WindowManagerServiceTests extends WindowTestsBase {
verify(mWm.mAtmService).setFocusedTask(tappedTask.mTaskId);
}
+
+ @Test
+ public void testDismissKeyguardCanWakeUp() {
+ doReturn(true).when(mWm).checkCallingPermission(anyString(), anyString());
+ spyOn(mWm.mAtmInternal);
+ doReturn(true).when(mWm.mAtmInternal).isDreaming();
+ doNothing().when(mWm.mAtmService.mStackSupervisor).wakeUp(anyString());
+ mWm.dismissKeyguard(null, "test-dismiss-keyguard");
+ verify(mWm.mAtmService.mStackSupervisor).wakeUp(anyString());
+ }
}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index edbdd4e94ac8..b6506b408714 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;
@@ -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);
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..a73e73e37fff 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) {
+ 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() {
+ 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..f4fc185d3b5c 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;
@@ -1014,12 +1042,15 @@ public class VoiceInteractionManagerService extends SystemService {
final long caller = Binder.clearCallingIdentity();
boolean deleted = false;
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/telephony/api/system-current.txt b/telephony/api/system-current.txt
index ec8e8e7910e8..0a7e1581815e 100644
--- a/telephony/api/system-current.txt
+++ b/telephony/api/system-current.txt
@@ -27,6 +27,26 @@ 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 ERROR_FDN_CHECK_FAILURE = 2; // 0x2
+ field public static final int ERROR_NOT_SUPPORTED = 3; // 0x3
+ field public static final int ERROR_UNKNOWN = 1; // 0x1
+ 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
+ field public static final int SUCCESS = 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);
@@ -656,6 +676,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);
@@ -732,6 +754,8 @@ 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 setCallWaitingStatus(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);
@@ -769,6 +793,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
@@ -823,6 +851,11 @@ 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);
+ }
+
public final class UiccAccessRule implements android.os.Parcelable {
ctor public UiccAccessRule(byte[], @Nullable String, long);
method public int describeContents();
diff --git a/telephony/java/android/telephony/CallForwardingInfo.java b/telephony/java/android/telephony/CallForwardingInfo.java
index 7e777fae46eb..2106f7fc4bb8 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,35 @@ 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
+ * Indicates that the operation was successful.
*/
- public static final int STATUS_INACTIVE = 0;
+ public static final int SUCCESS = 0;
/**
- * Indicates the call forwarding status is active.
- *
- * @hide
+ * Indicates that setting or retrieving the call forwarding info failed with an unknown error.
*/
- public static final int STATUS_ACTIVE = 1;
+ public static final int ERROR_UNKNOWN = 1;
/**
- * Indicates the call forwarding could not be enabled because the recipient is not on
+ * Indicates that call forwarding is not enabled because the recipient is not on a
* Fixed Dialing Number (FDN) list.
- *
- * @hide
*/
- public static final int STATUS_FDN_CHECK_FAILURE = 2;
+ public static final int ERROR_FDN_CHECK_FAILURE = 2;
/**
- * Indicates the call forwarding status is with an unknown error.
- *
- * @hide
+ * Indicates that call forwarding is not supported on the network at this time.
*/
- public static final int STATUS_UNKNOWN_ERROR = 3;
+ public static final int ERROR_NOT_SUPPORTED = 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 +70,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 +77,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 +84,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 +92,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 +100,25 @@ 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
+ * Call forwarding errors
+ * @hide
*/
- @IntDef(prefix = { "STATUS_" }, value = {
- STATUS_ACTIVE,
- STATUS_INACTIVE,
- STATUS_UNKNOWN_ERROR,
- STATUS_NOT_SUPPORTED,
- STATUS_FDN_CHECK_FAILURE
+ @IntDef(prefix = { "ERROR_" }, value = {
+ ERROR_UNKNOWN,
+ ERROR_NOT_SUPPORTED,
+ ERROR_FDN_CHECK_FAILURE
})
@Retention(RetentionPolicy.SOURCE)
- public @interface CallForwardingStatus {
+ public @interface CallForwardingError{
}
/**
* Call forwarding reason types
+ * @hide
*/
@IntDef(flag = true, prefix = { "REASON_" }, value = {
REASON_UNCONDITIONAL,
@@ -152,9 +133,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 +159,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.
- *
- * @return the call forwarding status.
+ * Whether call forwarding is enabled for the reason from {@link #getReason()}.
*
- * @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 +197,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 +205,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 +230,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 +254,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 +265,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 +286,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 4de1abfee7fc..47bc566a331a 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -32,6 +32,7 @@ import android.os.RemoteException;
import android.service.carrier.CarrierService;
import android.telecom.TelecomManager;
import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsSsData;
import com.android.internal.telephony.ICarrierConfigLoader;
import com.android.telephony.Rlog;
@@ -66,6 +67,18 @@ public class CarrierConfigManager {
public static final String EXTRA_SUBSCRIPTION_INDEX =
SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX;
+ /**
+ * 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;
+
+ /**
+ * Service class flag for voice telephony.
+ * Reference: 3GPP TS 27.007 Section 7.4 Facility lock +CLCK
+ */
+ public static final int SERVICE_CLASS_VOICE = ImsSsData.SERVICE_CLASS_VOICE;
+
private final Context mContext;
/**
@@ -212,6 +225,18 @@ public class CarrierConfigManager {
"call_barring_supports_deactivate_all_bool";
/**
+ * Specifies the service class for call barring service. Default value is
+ * {@link #SERVICE_CLASS_VOICE}.
+ * The value set as below:
+ * <ul>
+ * <li>0: {@link #SERVICE_CLASS_NONE}</li>
+ * <li>1: {@link #SERVICE_CLASS_VOICE}</li>
+ * </ul>
+ */
+ public static final String KEY_CALL_BARRING_DEFAULT_SERVICE_CLASS_INT =
+ "call_barring_default_service_class_int";
+
+ /**
* Flag indicating whether the Phone app should ignore EVENT_SIM_NETWORK_LOCKED
* events from the Sim.
* If true, this will prevent the IccNetworkDepersonalizationPanel from being shown, and
@@ -3889,10 +3914,23 @@ public class CarrierConfigManager {
* Indicating whether DUN APN should be disabled when the device is roaming. In that case,
* the default APN (i.e. internet) will be used for tethering.
*
+ * This config is only available when using Preset APN(not user edited) as Preferred APN.
+ *
+ * @hide
+ */
+ public static final String KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL =
+ "disable_dun_apn_while_roaming_with_preset_apn_bool";
+
+ /**
+ * Where there is no preferred APN, specifies the carrier's default preferred APN.
+ * Specifies the {@link android.provider.Telephony.Carriers.APN} of the default preferred apn.
+ *
+ * This config is only available with Preset APN(not user edited).
+ *
* @hide
*/
- public static final String KEY_DISABLE_DUN_APN_WHILE_ROAMING =
- "disable_dun_apn_while_roaming";
+ public static final String KEY_DEFAULT_PREFERRED_APN_NAME_STRING =
+ "default_preferred_apn_name_string";
/** The default value for every variable. */
private final static PersistableBundle sDefaults;
@@ -3968,6 +4006,7 @@ public class CarrierConfigManager {
sDefaults.putBoolean(KEY_CALL_BARRING_VISIBILITY_BOOL, false);
sDefaults.putBoolean(KEY_CALL_BARRING_SUPPORTS_PASSWORD_CHANGE_BOOL, true);
sDefaults.putBoolean(KEY_CALL_BARRING_SUPPORTS_DEACTIVATE_ALL_BOOL, true);
+ sDefaults.putInt(KEY_CALL_BARRING_DEFAULT_SERVICE_CLASS_INT, SERVICE_CLASS_VOICE);
sDefaults.putBoolean(KEY_CALL_FORWARDING_VISIBILITY_BOOL, true);
sDefaults.putBoolean(KEY_CALL_FORWARDING_WHEN_UNREACHABLE_SUPPORTED_BOOL, true);
sDefaults.putBoolean(KEY_CALL_FORWARDING_WHEN_UNANSWERED_SUPPORTED_BOOL, true);
@@ -4432,7 +4471,8 @@ public class CarrierConfigManager {
"ims:2", "cbs:2", "ia:2", "emergency:2", "mcx:3", "xcap:3"
});
sDefaults.putStringArray(KEY_MISSED_INCOMING_CALL_SMS_PATTERN_STRING_ARRAY, new String[0]);
- sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING, false);
+ sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
+ sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
}
/**
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/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 969016b1c017..0ec27933dc36 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -99,6 +99,8 @@ 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;
@@ -306,6 +308,8 @@ public class TelephonyManager {
private static boolean sServiceHandleCacheEnabled = true;
@GuardedBy("sCacheLock")
+ private static ITelephony sITelephony;
+ @GuardedBy("sCacheLock")
private static IPhoneSubInfo sIPhoneSubInfo;
@GuardedBy("sCacheLock")
private static ISub sISub;
@@ -4180,7 +4184,7 @@ public class TelephonyManager {
}
}
- /**
+ /**
* @param keyAvailability bitmask that defines the availabilty of keys for a type.
* @param keyType the key type which is being checked. (WLAN, EPDG)
* @return true if the digit at position keyType is 1, else false.
@@ -5499,13 +5503,39 @@ public class TelephonyManager {
}
}
- /**
- * @hide
- */
+ /**
+ * @hide
+ */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
private ITelephony getITelephony() {
- return ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
- .getTelephonyServiceManager().getTelephonyServiceRegisterer().get());
+ // Keeps cache disabled until test fixes are checked into AOSP.
+ if (!sServiceHandleCacheEnabled) {
+ return ITelephony.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getTelephonyServiceRegisterer()
+ .get());
+ }
+
+ if (sITelephony == null) {
+ ITelephony temp = ITelephony.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getTelephonyServiceRegisterer()
+ .get());
+ synchronized (sCacheLock) {
+ if (sITelephony == null && temp != null) {
+ try {
+ sITelephony = temp;
+ sITelephony.asBinder().linkToDeath(sServiceDeath, 0);
+ } catch (Exception e) {
+ // something has gone horribly wrong
+ sITelephony = null;
+ }
+ }
+ }
+ }
+ return sITelephony;
}
private IOns getIOns() {
@@ -12992,195 +13022,304 @@ 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 {
+ /**
+ * 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(@CallForwardingInfo.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 CallForwardingInfo#SUCCESS} if the operation
+ * succeeded and an error code from {@link CallForwardingInfo}
+ * if 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 @CallForwardingInfo.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.
+ *
+ * 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.
*
- * @return the status of call waiting function.
+ * @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 setCallWaitingStatus(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;
}
/**
@@ -13401,6 +13540,10 @@ public class TelephonyManager {
*/
private static void resetServiceCache() {
synchronized (sCacheLock) {
+ if (sITelephony != null) {
+ sITelephony.asBinder().unlinkToDeath(sServiceDeath, 0);
+ sITelephony = null;
+ }
if (sISub != null) {
sISub.asBinder().unlinkToDeath(sServiceDeath, 0);
sISub = null;
@@ -13417,9 +13560,9 @@ public class TelephonyManager {
}
}
- /**
- * @hide
- */
+ /**
+ * @hide
+ */
static IPhoneSubInfo getSubscriberInfoService() {
// Keeps cache disabled until test fixes are checked into AOSP.
if (!sServiceHandleCacheEnabled) {
diff --git a/telephony/java/android/telephony/gsm/GsmCellLocation.java b/telephony/java/android/telephony/gsm/GsmCellLocation.java
index 30cea0e6dd59..bc8ee1dd9359 100644
--- a/telephony/java/android/telephony/gsm/GsmCellLocation.java
+++ b/telephony/java/android/telephony/gsm/GsmCellLocation.java
@@ -55,7 +55,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/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index 2b3072eefe2e..da7311c08307 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -137,18 +137,30 @@ public class ImsService extends Service {
}
@Override
- public IImsMmTelFeature createMmTelFeature(int slotId, IImsFeatureStatusCallback c) {
- return createMmTelFeatureInternal(slotId, c);
+ public IImsMmTelFeature createMmTelFeature(int slotId) {
+ return createMmTelFeatureInternal(slotId);
}
@Override
- public IImsRcsFeature createRcsFeature(int slotId, IImsFeatureStatusCallback c) {
- return createRcsFeatureInternal(slotId, c);
+ public IImsRcsFeature createRcsFeature(int slotId) {
+ return createRcsFeatureInternal(slotId);
}
@Override
- public void removeImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c) {
- ImsService.this.removeImsFeature(slotId, featureType, c);
+ public void addFeatureStatusCallback(int slotId, int featureType,
+ IImsFeatureStatusCallback c) {
+ ImsService.this.addImsFeatureStatusCallback(slotId, featureType, c);
+ }
+
+ @Override
+ public void removeFeatureStatusCallback(int slotId, int featureType,
+ IImsFeatureStatusCallback c) {
+ ImsService.this.removeImsFeatureStatusCallback(slotId, featureType, c);
+ }
+
+ @Override
+ public void removeImsFeature(int slotId, int featureType) {
+ ImsService.this.removeImsFeature(slotId, featureType);
}
@Override
@@ -204,11 +216,10 @@ public class ImsService extends Service {
return mFeaturesBySlot.get(slotId);
}
- private IImsMmTelFeature createMmTelFeatureInternal(int slotId,
- IImsFeatureStatusCallback c) {
+ private IImsMmTelFeature createMmTelFeatureInternal(int slotId) {
MmTelFeature f = createMmTelFeature(slotId);
if (f != null) {
- setupFeature(f, slotId, ImsFeature.FEATURE_MMTEL, c);
+ setupFeature(f, slotId, ImsFeature.FEATURE_MMTEL);
return f.getBinder();
} else {
Log.e(LOG_TAG, "createMmTelFeatureInternal: null feature returned.");
@@ -216,11 +227,10 @@ public class ImsService extends Service {
}
}
- private IImsRcsFeature createRcsFeatureInternal(int slotId,
- IImsFeatureStatusCallback c) {
+ private IImsRcsFeature createRcsFeatureInternal(int slotId) {
RcsFeature f = createRcsFeature(slotId);
if (f != null) {
- setupFeature(f, slotId, ImsFeature.FEATURE_RCS, c);
+ setupFeature(f, slotId, ImsFeature.FEATURE_RCS);
return f.getBinder();
} else {
Log.e(LOG_TAG, "createRcsFeatureInternal: null feature returned.");
@@ -228,13 +238,45 @@ public class ImsService extends Service {
}
}
- private void setupFeature(ImsFeature f, int slotId, int featureType,
- IImsFeatureStatusCallback c) {
+ private void setupFeature(ImsFeature f, int slotId, int featureType) {
f.initialize(this, slotId);
- f.addImsFeatureStatusCallback(c);
addImsFeature(slotId, featureType, f);
}
+ private void addImsFeatureStatusCallback(int slotId, int featureType,
+ IImsFeatureStatusCallback c) {
+ synchronized (mFeaturesBySlot) {
+ // get ImsFeature associated with the slot/feature
+ SparseArray<ImsFeature> features = mFeaturesBySlot.get(slotId);
+ if (features == null) {
+ Log.w(LOG_TAG, "Can not add ImsFeatureStatusCallback - no features on slot "
+ + slotId);
+ return;
+ }
+ ImsFeature f = features.get(featureType);
+ if (f != null) {
+ f.addImsFeatureStatusCallback(c);
+ }
+ }
+ }
+
+ private void removeImsFeatureStatusCallback(int slotId, int featureType,
+ IImsFeatureStatusCallback c) {
+ synchronized (mFeaturesBySlot) {
+ // get ImsFeature associated with the slot/feature
+ SparseArray<ImsFeature> features = mFeaturesBySlot.get(slotId);
+ if (features == null) {
+ Log.w(LOG_TAG, "Can not remove ImsFeatureStatusCallback - no features on slot "
+ + slotId);
+ return;
+ }
+ ImsFeature f = features.get(featureType);
+ if (f != null) {
+ f.removeImsFeatureStatusCallback(c);
+ }
+ }
+ }
+
private void addImsFeature(int slotId, int featureType, ImsFeature f) {
synchronized (mFeaturesBySlot) {
// Get SparseArray for Features, by querying slot Id
@@ -248,8 +290,7 @@ public class ImsService extends Service {
}
}
- private void removeImsFeature(int slotId, int featureType,
- IImsFeatureStatusCallback c) {
+ private void removeImsFeature(int slotId, int featureType) {
synchronized (mFeaturesBySlot) {
// get ImsFeature associated with the slot/feature
SparseArray<ImsFeature> features = mFeaturesBySlot.get(slotId);
@@ -264,7 +305,6 @@ public class ImsService extends Service {
+ featureType + " exists on slot " + slotId);
return;
}
- f.removeImsFeatureStatusCallback(c);
f.onFeatureRemoved();
features.remove(featureType);
}
diff --git a/telephony/java/android/telephony/ims/aidl/IImsServiceController.aidl b/telephony/java/android/telephony/ims/aidl/IImsServiceController.aidl
index c7da681b86a3..c956cbcc816c 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsServiceController.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsServiceController.aidl
@@ -31,12 +31,14 @@ import com.android.ims.internal.IImsFeatureStatusCallback;
*/
interface IImsServiceController {
void setListener(IImsServiceControllerListener l);
- IImsMmTelFeature createMmTelFeature(int slotId, in IImsFeatureStatusCallback c);
- IImsRcsFeature createRcsFeature(int slotId, in IImsFeatureStatusCallback c);
+ IImsMmTelFeature createMmTelFeature(int slotId);
+ IImsRcsFeature createRcsFeature(int slotId);
ImsFeatureConfiguration querySupportedImsFeatures();
+ void addFeatureStatusCallback(int slotId, int featureType, in IImsFeatureStatusCallback c);
+ void removeFeatureStatusCallback(int slotId, int featureType, in IImsFeatureStatusCallback c);
// Synchronous call to ensure the ImsService is ready before continuing with feature creation.
void notifyImsServiceReadyForFeatureCreation();
- void removeImsFeature(int slotId, int featureType, in IImsFeatureStatusCallback c);
+ void removeImsFeature(int slotId, int featureType);
IImsConfig getConfig(int slotId);
IImsRegistration getRegistration(int slotId);
oneway void enableIms(int slotId);
diff --git a/telephony/java/android/telephony/ims/compat/ImsService.java b/telephony/java/android/telephony/ims/compat/ImsService.java
index eafbb14539f5..41d1d726b3f4 100644
--- a/telephony/java/android/telephony/ims/compat/ImsService.java
+++ b/telephony/java/android/telephony/ims/compat/ImsService.java
@@ -21,7 +21,6 @@ import android.app.Service;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
import android.os.IBinder;
-import android.os.RemoteException;
import android.telephony.CarrierConfigManager;
import android.telephony.ims.compat.feature.ImsFeature;
import android.telephony.ims.compat.feature.MMTelFeature;
@@ -91,25 +90,35 @@ public class ImsService extends Service {
protected final IBinder mImsServiceController = new IImsServiceController.Stub() {
@Override
- public IImsMMTelFeature createEmergencyMMTelFeature(int slotId,
- IImsFeatureStatusCallback c) {
- return createEmergencyMMTelFeatureInternal(slotId, c);
+ public IImsMMTelFeature createEmergencyMMTelFeature(int slotId) {
+ return createEmergencyMMTelFeatureInternal(slotId);
}
@Override
- public IImsMMTelFeature createMMTelFeature(int slotId, IImsFeatureStatusCallback c) {
- return createMMTelFeatureInternal(slotId, c);
+ public IImsMMTelFeature createMMTelFeature(int slotId) {
+ return createMMTelFeatureInternal(slotId);
}
@Override
- public IImsRcsFeature createRcsFeature(int slotId, IImsFeatureStatusCallback c) {
- return createRcsFeatureInternal(slotId, c);
+ public IImsRcsFeature createRcsFeature(int slotId) {
+ return createRcsFeatureInternal(slotId);
}
@Override
- public void removeImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c)
- throws RemoteException {
- ImsService.this.removeImsFeature(slotId, featureType, c);
+ public void removeImsFeature(int slotId, int featureType) {
+ ImsService.this.removeImsFeature(slotId, featureType);
+ }
+
+ @Override
+ public void addFeatureStatusCallback(int slotId, int featureType,
+ IImsFeatureStatusCallback c) {
+ addImsFeatureStatusCallback(slotId, featureType, c);
+ }
+
+ @Override
+ public void removeFeatureStatusCallback(int slotId, int featureType,
+ IImsFeatureStatusCallback c) {
+ removeImsFeatureStatusCallback(slotId, featureType, c);
}
};
@@ -137,46 +146,40 @@ public class ImsService extends Service {
return mFeaturesBySlot.get(slotId);
}
- private IImsMMTelFeature createEmergencyMMTelFeatureInternal(int slotId,
- IImsFeatureStatusCallback c) {
+ private IImsMMTelFeature createEmergencyMMTelFeatureInternal(int slotId) {
MMTelFeature f = onCreateEmergencyMMTelImsFeature(slotId);
if (f != null) {
- setupFeature(f, slotId, ImsFeature.EMERGENCY_MMTEL, c);
+ setupFeature(f, slotId, ImsFeature.EMERGENCY_MMTEL);
return f.getBinder();
} else {
return null;
}
}
- private IImsMMTelFeature createMMTelFeatureInternal(int slotId,
- IImsFeatureStatusCallback c) {
+ private IImsMMTelFeature createMMTelFeatureInternal(int slotId) {
MMTelFeature f = onCreateMMTelImsFeature(slotId);
if (f != null) {
- setupFeature(f, slotId, ImsFeature.MMTEL, c);
+ setupFeature(f, slotId, ImsFeature.MMTEL);
return f.getBinder();
} else {
return null;
}
}
- private IImsRcsFeature createRcsFeatureInternal(int slotId,
- IImsFeatureStatusCallback c) {
+ private IImsRcsFeature createRcsFeatureInternal(int slotId) {
RcsFeature f = onCreateRcsFeature(slotId);
if (f != null) {
- setupFeature(f, slotId, ImsFeature.RCS, c);
+ setupFeature(f, slotId, ImsFeature.RCS);
return f.getBinder();
} else {
return null;
}
}
- private void setupFeature(ImsFeature f, int slotId, int featureType,
- IImsFeatureStatusCallback c) {
+ private void setupFeature(ImsFeature f, int slotId, int featureType) {
f.setContext(this);
f.setSlotId(slotId);
- f.addImsFeatureStatusCallback(c);
addImsFeature(slotId, featureType, f);
- // TODO: Remove once new onFeatureReady AIDL is merged in.
f.onFeatureReady();
}
@@ -193,12 +196,45 @@ public class ImsService extends Service {
}
}
- private void removeImsFeature(int slotId, int featureType,
+ private void addImsFeatureStatusCallback(int slotId, int featureType,
+ IImsFeatureStatusCallback c) {
+ synchronized (mFeaturesBySlot) {
+ // get ImsFeature associated with the slot/feature
+ SparseArray<ImsFeature> features = mFeaturesBySlot.get(slotId);
+ if (features == null) {
+ Log.w(LOG_TAG, "Can not add ImsFeatureStatusCallback. No ImsFeatures exist on"
+ + " slot " + slotId);
+ return;
+ }
+ ImsFeature f = features.get(featureType);
+ if (f != null) {
+ f.addImsFeatureStatusCallback(c);
+ }
+ }
+ }
+
+ private void removeImsFeatureStatusCallback(int slotId, int featureType,
IImsFeatureStatusCallback c) {
synchronized (mFeaturesBySlot) {
// get ImsFeature associated with the slot/feature
SparseArray<ImsFeature> features = mFeaturesBySlot.get(slotId);
if (features == null) {
+ Log.w(LOG_TAG, "Can not remove ImsFeatureStatusCallback. No ImsFeatures exist on"
+ + " slot " + slotId);
+ return;
+ }
+ ImsFeature f = features.get(featureType);
+ if (f != null) {
+ f.removeImsFeatureStatusCallback(c);
+ }
+ }
+ }
+
+ private void removeImsFeature(int slotId, int featureType) {
+ synchronized (mFeaturesBySlot) {
+ // get ImsFeature associated with the slot/feature
+ SparseArray<ImsFeature> features = mFeaturesBySlot.get(slotId);
+ if (features == null) {
Log.w(LOG_TAG, "Can not remove ImsFeature. No ImsFeatures exist on slot "
+ slotId);
return;
@@ -209,7 +245,6 @@ public class ImsService extends Service {
+ featureType + " exists on slot " + slotId);
return;
}
- f.removeImsFeatureStatusCallback(c);
f.onFeatureRemoved();
features.remove(featureType);
}
diff --git a/telephony/java/com/android/ims/internal/IImsServiceController.aidl b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
index 857089fac33a..e9528f474082 100644
--- a/telephony/java/com/android/ims/internal/IImsServiceController.aidl
+++ b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
@@ -25,8 +25,10 @@ import com.android.ims.internal.IImsRcsFeature;
* {@hide}
*/
interface IImsServiceController {
- IImsMMTelFeature createEmergencyMMTelFeature(int slotId, in IImsFeatureStatusCallback c);
- IImsMMTelFeature createMMTelFeature(int slotId, in IImsFeatureStatusCallback c);
- IImsRcsFeature createRcsFeature(int slotId, in IImsFeatureStatusCallback c);
- void removeImsFeature(int slotId, int featureType, in IImsFeatureStatusCallback c);
+ IImsMMTelFeature createEmergencyMMTelFeature(int slotId);
+ IImsMMTelFeature createMMTelFeature(int slotId);
+ IImsRcsFeature createRcsFeature(int slotId);
+ void removeImsFeature(int slotId, int featureType);
+ void addFeatureStatusCallback(int slotId, int featureType, in IImsFeatureStatusCallback c);
+ void removeFeatureStatusCallback(int slotId, int featureType, in IImsFeatureStatusCallback c);
}
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..02a74ba53ccb 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;
@@ -1648,78 +1649,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
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index a53ea16ba7c4..d430db525f4c 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -29,7 +29,8 @@ android_test {
"flickerlib",
"truth-prebuilt",
"launcher-helper-lib",
- "launcher-aosp-tapl"
+ "launcher-aosp-tapl",
+ "platform-test-annotations",
],
}
@@ -50,6 +51,7 @@ android_test {
"truth-prebuilt",
"app-helpers-core",
"launcher-helper-lib",
- "launcher-aosp-tapl"
+ "launcher-aosp-tapl",
+ "platform-test-annotations",
],
} \ 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 b64811b2767d..c1ba21a2977a 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
@@ -16,6 +16,7 @@
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
@@ -39,6 +40,7 @@ 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)
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 0940c192517e..2c0072270378 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
@@ -16,6 +16,7 @@
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
@@ -39,6 +40,7 @@ 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)
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 c2e87dbbf24b..4697adcbce80 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
@@ -16,6 +16,7 @@
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
@@ -40,6 +41,7 @@ import org.junit.runners.Parameterized
* Test IME window closing to home transitions.
* To run this test: `atest FlickerTests:CloseImeWindowToHomeTest`
*/
+@Presubmit
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
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 11ccb69c46cf..2caa8f399be7 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
@@ -16,6 +16,7 @@
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
@@ -39,6 +40,7 @@ import org.junit.runners.Parameterized
* Test IME window opening transitions.
* To run this test: `atest FlickerTests:OpenImeWindowTest`
*/
+@Presubmit
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
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 17590723da8d..2c9c8ba75a10 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
@@ -16,6 +16,7 @@
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
@@ -38,6 +39,7 @@ import org.junit.runners.Parameterized
* Test cold launch app from launcher.
* To run this test: `atest FlickerTests:OpenAppColdTest`
*/
+@Presubmit
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
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 87c863338edb..7447bdae9abe 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
@@ -16,6 +16,7 @@
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
@@ -43,6 +44,7 @@ import org.junit.runners.Parameterized
* Test open app to split screen.
* To run this test: `atest FlickerTests:SplitScreenToLauncherTest`
*/
+@Presubmit
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
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/wifi/api/system-current.txt b/wifi/api/system-current.txt
index c3e573c311c2..aab4a2df7633 100644
--- a/wifi/api/system-current.txt
+++ b/wifi/api/system-current.txt
@@ -292,6 +292,7 @@ package android.net.wifi {
method public int getBandwidth();
method @Nullable public android.net.MacAddress getBssid();
method public int getFrequency();
+ method public int getWifiStandard();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field public static final int CHANNEL_WIDTH_160MHZ = 6; // 0x6
field public static final int CHANNEL_WIDTH_20MHZ = 2; // 0x2
@@ -331,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;
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index 54ec1e1c7c0b..329ca37407de 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -587,12 +587,18 @@ public final class ScanResult implements Parcelable {
* 6 GHz band frequency of first channel in MHz
* @hide
*/
- public static final int BAND_6_GHZ_START_FREQ_MHZ = 5945;
+ public static final int BAND_6_GHZ_START_FREQ_MHZ = 5955;
/**
* 6 GHz band frequency of last channel in MHz
* @hide
*/
- public static final int BAND_6_GHZ_END_FREQ_MHZ = 7105;
+ public static final int BAND_6_GHZ_END_FREQ_MHZ = 7115;
+
+ /**
+ * 6 GHz band operating class 136 channel 2 center frequency in MHz
+ * @hide
+ */
+ public static final int BAND_6_GHZ_OP_CLASS_136_CH_2_FREQ_MHZ = 5935;
/**
* 60 GHz band first channel number
@@ -645,7 +651,10 @@ public final class ScanResult implements Parcelable {
* @hide
*/
public static boolean is6GHz(int freqMhz) {
- return freqMhz >= BAND_6_GHZ_START_FREQ_MHZ && freqMhz <= BAND_6_GHZ_END_FREQ_MHZ;
+ if (freqMhz == BAND_6_GHZ_OP_CLASS_136_CH_2_FREQ_MHZ) {
+ return true;
+ }
+ return (freqMhz >= BAND_6_GHZ_START_FREQ_MHZ && freqMhz <= BAND_6_GHZ_END_FREQ_MHZ);
}
/**
@@ -687,6 +696,9 @@ public final class ScanResult implements Parcelable {
}
if (band == WifiScanner.WIFI_BAND_6_GHZ) {
if (channel >= BAND_6_GHZ_FIRST_CH_NUM && channel <= BAND_6_GHZ_LAST_CH_NUM) {
+ if (channel == 2) {
+ return BAND_6_GHZ_OP_CLASS_136_CH_2_FREQ_MHZ;
+ }
return ((channel - BAND_6_GHZ_FIRST_CH_NUM) * 5) + BAND_6_GHZ_START_FREQ_MHZ;
} else {
return UNSPECIFIED;
@@ -711,6 +723,9 @@ public final class ScanResult implements Parcelable {
} else if (is5GHz(freqMhz)) {
return ((freqMhz - BAND_5_GHZ_START_FREQ_MHZ) / 5) + BAND_5_GHZ_FIRST_CH_NUM;
} else if (is6GHz(freqMhz)) {
+ if (freqMhz == BAND_6_GHZ_OP_CLASS_136_CH_2_FREQ_MHZ) {
+ return 2;
+ }
return ((freqMhz - BAND_6_GHZ_START_FREQ_MHZ) / 5) + BAND_6_GHZ_FIRST_CH_NUM;
}
diff --git a/wifi/java/android/net/wifi/SoftApInfo.java b/wifi/java/android/net/wifi/SoftApInfo.java
index 40981f7b0fb1..cf61f81e6d22 100644
--- a/wifi/java/android/net/wifi/SoftApInfo.java
+++ b/wifi/java/android/net/wifi/SoftApInfo.java
@@ -86,8 +86,6 @@ public final class SoftApInfo implements Parcelable {
*/
public static final int CHANNEL_WIDTH_160MHZ = 6;
-
-
/** The frequency which AP resides on. */
private int mFrequency = 0;
@@ -99,6 +97,11 @@ public final class SoftApInfo implements Parcelable {
private MacAddress mBssid;
/**
+ * The operational mode of the AP.
+ */
+ private @WifiAnnotations.WifiStandard int mWifiStandard = ScanResult.WIFI_STANDARD_UNKNOWN;
+
+ /**
* Get the frequency which AP resides on.
*/
public int getFrequency() {
@@ -163,6 +166,27 @@ public final class SoftApInfo implements Parcelable {
}
/**
+ * Set the operational mode of the AP.
+ *
+ * @param wifiStandard values from {@link ScanResult}'s {@code WIFI_STANDARD_}
+ * @hide
+ */
+ public void setWifiStandard(@WifiAnnotations.WifiStandard int wifiStandard) {
+ mWifiStandard = wifiStandard;
+ }
+
+ /**
+ * Get the operational mode of the AP.
+ * @return valid values from {@link ScanResult}'s {@code WIFI_STANDARD_}
+ */
+ public @WifiAnnotations.WifiStandard int getWifiStandard() {
+ if (!SdkLevelUtil.isAtLeastS()) {
+ throw new UnsupportedOperationException();
+ }
+ return mWifiStandard;
+ }
+
+ /**
* @hide
*/
public SoftApInfo(@Nullable SoftApInfo source) {
@@ -170,6 +194,7 @@ public final class SoftApInfo implements Parcelable {
mFrequency = source.mFrequency;
mBandwidth = source.mBandwidth;
mBssid = source.mBssid;
+ mWifiStandard = source.mWifiStandard;
}
}
@@ -191,6 +216,7 @@ public final class SoftApInfo implements Parcelable {
dest.writeInt(mFrequency);
dest.writeInt(mBandwidth);
dest.writeParcelable(mBssid, flags);
+ dest.writeInt(mWifiStandard);
}
@NonNull
@@ -201,6 +227,7 @@ public final class SoftApInfo implements Parcelable {
info.mFrequency = in.readInt();
info.mBandwidth = in.readInt();
info.mBssid = in.readParcelable(MacAddress.class.getClassLoader());
+ info.mWifiStandard = in.readInt();
return info;
}
@@ -215,8 +242,9 @@ public final class SoftApInfo implements Parcelable {
StringBuilder sbuf = new StringBuilder();
sbuf.append("SoftApInfo{");
sbuf.append("bandwidth= ").append(mBandwidth);
- sbuf.append(",frequency= ").append(mFrequency);
+ sbuf.append(", frequency= ").append(mFrequency);
if (mBssid != null) sbuf.append(",bssid=").append(mBssid.toString());
+ sbuf.append(", wifiStandard= ").append(mWifiStandard);
sbuf.append("}");
return sbuf.toString();
}
@@ -228,11 +256,12 @@ public final class SoftApInfo implements Parcelable {
SoftApInfo softApInfo = (SoftApInfo) o;
return mFrequency == softApInfo.mFrequency
&& mBandwidth == softApInfo.mBandwidth
- && Objects.equals(mBssid, softApInfo.mBssid);
+ && Objects.equals(mBssid, softApInfo.mBssid)
+ && mWifiStandard == softApInfo.mWifiStandard;
}
@Override
public int hashCode() {
- return Objects.hash(mFrequency, mBandwidth, mBssid);
+ return Objects.hash(mFrequency, mBandwidth, mBssid, mWifiStandard);
}
}
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 02b7a42d3878..391be357e963 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -1206,12 +1206,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.
@@ -2060,7 +2066,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 {}
/**
@@ -2078,6 +2085,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.
*
@@ -2281,6 +2295,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)) {
@@ -2837,6 +2854,7 @@ public class WifiConfiguration implements Parcelable {
mRandomizedMacAddress = source.mRandomizedMacAddress;
macRandomizationSetting = source.macRandomizationSetting;
randomizedMacExpirationTimeMs = source.randomizedMacExpirationTimeMs;
+ randomizedMacLastModifiedTimeMs = source.randomizedMacLastModifiedTimeMs;
requirePmf = source.requirePmf;
updateIdentifier = source.updateIdentifier;
carrierId = source.carrierId;
@@ -2911,6 +2929,7 @@ 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);
}
@@ -2985,6 +3004,7 @@ 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();
return config;
diff --git a/wifi/tests/src/android/net/wifi/ScanResultTest.java b/wifi/tests/src/android/net/wifi/ScanResultTest.java
index 4a3586826de9..59b88a7f0c3f 100644
--- a/wifi/tests/src/android/net/wifi/ScanResultTest.java
+++ b/wifi/tests/src/android/net/wifi/ScanResultTest.java
@@ -103,9 +103,10 @@ public class ScanResultTest {
5845, WifiScanner.WIFI_BAND_5_GHZ, 169,
5865, WifiScanner.WIFI_BAND_5_GHZ, 173,
/* Now some 6GHz channels */
- 5945, WifiScanner.WIFI_BAND_6_GHZ, 1,
- 5960, WifiScanner.WIFI_BAND_6_GHZ, 4,
- 6100, WifiScanner.WIFI_BAND_6_GHZ, 32
+ 5955, WifiScanner.WIFI_BAND_6_GHZ, 1,
+ 5935, WifiScanner.WIFI_BAND_6_GHZ, 2,
+ 5970, WifiScanner.WIFI_BAND_6_GHZ, 4,
+ 6110, WifiScanner.WIFI_BAND_6_GHZ, 32
};
/**
diff --git a/wifi/tests/src/android/net/wifi/SoftApInfoTest.java b/wifi/tests/src/android/net/wifi/SoftApInfoTest.java
index 458a95494438..2821c35f9347 100644
--- a/wifi/tests/src/android/net/wifi/SoftApInfoTest.java
+++ b/wifi/tests/src/android/net/wifi/SoftApInfoTest.java
@@ -17,6 +17,7 @@
package android.net.wifi;
import android.net.MacAddress;
+import android.net.wifi.util.SdkLevelUtil;
import android.os.Parcel;
import static org.junit.Assert.assertEquals;
@@ -40,6 +41,7 @@ public class SoftApInfoTest {
info.setFrequency(2412);
info.setBandwidth(SoftApInfo.CHANNEL_WIDTH_20MHZ);
info.setBssid(MacAddress.fromString("aa:bb:cc:dd:ee:ff"));
+ info.setWifiStandard(ScanResult.WIFI_STANDARD_LEGACY);
SoftApInfo copiedInfo = new SoftApInfo(info);
@@ -57,6 +59,7 @@ public class SoftApInfoTest {
info.setFrequency(2412);
info.setBandwidth(SoftApInfo.CHANNEL_WIDTH_20MHZ);
info.setBssid(MacAddress.fromString("aa:bb:cc:dd:ee:ff"));
+ info.setWifiStandard(ScanResult.WIFI_STANDARD_LEGACY);
Parcel parcelW = Parcel.obtain();
info.writeToParcel(parcelW, 0);
@@ -72,4 +75,19 @@ public class SoftApInfoTest {
assertEquals(info.hashCode(), fromParcel.hashCode());
}
+
+ /**
+ * Verifies the initial value same as expected.
+ */
+ @Test
+ public void testInitialValue() throws Exception {
+ SoftApInfo info = new SoftApInfo();
+ assertEquals(info.getFrequency(), 0);
+ assertEquals(info.getBandwidth(), SoftApInfo.CHANNEL_WIDTH_INVALID);
+ if (SdkLevelUtil.isAtLeastS()) {
+ assertEquals(info.getBssid(), null);
+ assertEquals(info.getWifiStandard(), ScanResult.WIFI_STANDARD_UNKNOWN);
+ }
+ }
+
}